创建自注册的Swift UI 控件

对于自定义控件来说,在不破坏原有的消息机制的前提下,如何响应事件通知?在本文中,我将演示一个通知代理类,通过一个简单的例子,我们用该类向已有的iOS UI控件中增加了自己的新功能:为Text View控件增加placeholder文本。

问题:缺失的Placeholder

Placeholder文本是用于在某些控件中提示用户输入指定类型的数据的良好方式。UIKit的UITextField控件的placeholder属性就是用来干这个的。例如,下图中,在Twitter的选项设置中,User Name字段就使用了placeholder文本。通过这个placeholder文本,用户可以知道应当在这里输入一个包含了@符号的Twitter用户名。而在Password字段的placeholder文本则标明该字段是必填的。

图 1 - 设置程序中的Placeholder 文本

尽管Text Filed有placeholder属性,但它的兄弟控件Text View却没有这个属性。

问题的解决

我准备这样解决这个问题:

  1. 创建一个UITextView的扩展,添加一个placeholder的计算属性
  2. 在扩展中,当TextView中没有输入任何文字的情况下,显示一个Label,如果输入有文字,则隐藏这个Label。

就像我在前面的文章中提到的,我更喜欢使用扩展,而不是子类。因为扩展允许我在App中使用“盒子之外”的UIKit控件。我需要做的仅仅是在项目中加入某个类的扩展文件,然后这个类会自动获得新的特性。

方式1:UITextViewDelegate

当用户在TextView中输入文本时,iOS有几种通知App的方式。其中一种就是通过UITextViewDelegate协议。在这个扩展中有一个对TextView的引用保存在它自己的delegate属性中。当用户输入或删除字符时,TextView的协议方法会被调用。我们可以在协议方法中加入隐藏和显示placeholder标签的代码。

这个解决方法的缺点是,Text View太过于依赖delegate属性,因为在一个UIControl中你只能注册一个委托对象。如果你需要向TextView中加入更多的委托对象,你就会比较麻烦。

方式2:NSNotificationCenter

NSNotificationCenter通过UITextViewTextDidChangeNotification通知来告诉你用户在TextView中输入或删除了某些字符。这是一种更好的选择,因为它不再依赖于delegate属性。缺点是需要向通知中心进行注销。一般,我们在对象的deinit方法中向NSNotificationCenter注销该对象。但是在Swift中,我们无法在扩展中使用deinit方法。那我们怎样才能突破这个限制呢?

创建一个通知代理

我们通过创建一个轻量级的代理对象来突破这个限制,这个代理对象代替TextView来向通知中心进行注册。

我已经在我的项目中创建了一个UITextView扩展以及一个代理类,你可以从这里下载。

双击.xcodeproj文件,用Xcode打开这个项目。在项目导航窗口中选中mmTextViewExtensions.swift文件,你可以找到这个通知代理类(如你所见,其中也包含了上一篇文章中的mmDyamicTypeExtensions类)。

下面是位于文件头部的通知代理类:

你可以看到,它是UIView子类。这样我们就可以将它当成subview添加到TextView中。addObserverForName:usingBLock:方法通知中心的方法拥有一样的签名。这个方法中只有一行代码,就是将TextView注册到通知中心并将参数传递过去,包括TextView的闭包,这样当通知发生时该闭包被执行。通知中心会返回一个NSObjectProtocol对象,稍后我们会用来进行通知的注销操作。

deinit方法也只有一行代码,仅仅是向通知中心进行对象的注销。

通知代理类是可重用的。你可以用于任何类型的通知以及你想接收通知的任何类型的对象。

然后是UITextView的扩展,在扩展中有一个placeholderLabel属性,如你所想,它引用了一个placeholder标签对象。

此外还有一个placeholder的字符串属性。在后面你将看到,所有的奇迹将在运行时发生,当placeholder属性被设置,这个计算属性的代码就会执行。

现在打开Main.storyboard文件。如图2所示,故事板中只有一个Scene,在这个Scene的顶部,有一个Text View。

图 2 - 主 scene 中包含了一个 text view

当你选择Text View,然后打开属性面板,你将看见一个Placeholder属性,它是扩展中增加的属性,这个属性的默认值是“Enter your text here!”。在设计时不会显示这段文本(要在设计时可见,我们需要花费大量的工作,因此这不是本文的主题),但在运行时它会显示。

The Notification Proxy at Run Time

运行时的通知代理

图3中的UML序列图显示了所有对象之间发生的消息传递的顺序。

图 3 - 运行时对象间消息传递的顺序

每一步的解释如下:

  1. 当运行时,UITextView的placeholder文本被改变时,该扩展的placeholder计算属性中的代码被触发。
  2. 扩展的addPlaceholderLabel方法被调用。
  3. addPlaceholderLabel方法创建一个placeholder标签并设置标签文本。
  4. placeholder标签被添加进UITextView。
  5. 一个通知代理对象被创建。
  6. 扩展调用通知代理的addObserverForName:withBlock:方法,并指定对UITextViewTextDidChangeNotification通知感兴趣。此外闭包代码会在通知发生时执行。
  7. 通知代理对象将UITextView注册到通知中心,同时将通知时间传递给TextView,当通知发生时,UITextView的闭包代码将被执行。
  8. UITextView以subview的方式添加通知代理对象。
  9. 当UITextViewTextDidChangeNotification通知发生,通知中心调用TextView扩展的闭包。
  10. UITextView扩展将placeholder标签的hidden属性设置为true和false,取决于TextView中的文本是否有内容。

    11.当TextView在运行时解构时,placeholder标签和通知代理对象都被解构。

  11. 当通知代理对象解构时,将TextView对象从通知中心中注销。

Let’s Take it for a Test Run!

运行测试

  1. In Xcode’s Scheme control, select one of the simulators such as iPhone 6.

    在Xcode的Scheme下拉列表中,选择一个模拟器,例如iPhone6。

  2. Click Xcode’s Run button.

    点击Xcode的Run按钮。

  3. When the app appears in the Simulator, you can see the placeholder text (Figure 4).

    当App在模拟器中运行后,我们可以看到placeholder文本(图4)。

图 4 - 运行时的 placeholder 文本

4.Now type some code in the text view. As soon as you begin typing, the placeholder label disappears (Figure 5).
现在在TextView中输入任意字符。此时,placeholder将消失(图5)。

图 5 -placeholder 文字消失了

如果将输入的字符删除干净,placeholder文字将重新出现。

结束语

要解决编程问题有许多方法。最好的方法是先看一下摆在你目前的选择,然后逐个分析其中的利弊。我希望你能看到本文中序列图的好处。它能让你将对象之间在运行时的交互画出来。我在编写代码时也使用了这个方法。你会发现它们能帮助你找出潜在的问题,更能帮你找出编程问题中的新的、更高效的解决方案。

时间: 2024-10-23 19:39:49

创建自注册的Swift UI 控件的相关文章

swift常用UI控件的使用方法

对于习惯了OC代码的程序员来说,swift的语法简直让人不能忍受,今天将一些常用的UI控件简单做了一下整理. import UIKit class ViewController : UIViewController, UIPickerViewDataSource, UIPickerViewDelegate { override func viewDidLoad() { super.viewDidLoad() self.view.backgroundColor = UIColor.whiteCol

[Swift通天遁地]九、拔剑吧-(4)使用开源类库创建可滑动的Segment分段控件

本文将演示创建多种自定义Segment分段样式的控件. 首先确保已经安装了所需的第三方类库.双击查看安装配置文件[Podfile] 1 platform :ios, ‘12.0’ 2 use_frameworks! 3 4 target 'DemoApp' do 5 source 'https://github.com/CocoaPods/Specs.git' 6 pod 'TwicketSegmentedControl' 7 end 根据配置文件中的相关设置,安装第三方类库. 安装完成之后,双

快速创建UI控件的 方法 ,值得总结

在平常写代码的时候相信大家 都会为每次创建 button或者其他的系统控件的 那么多行代码而烦恼 ,那么怎么能有个简单的方法来快速创建一个button呢.废话不多说,直接进入主题! 1.第一种方法  便利构造器 写一个UIButton的子类,添加一个便利构造器的方法,将所要传递的参数 都直接在调用便利构造器的方法时候 传递进去  ,然后返回一个  你想要的 button. 2.是用代码块 在xcode中 有一种可以快速创建代码的 功能 ,就是代码块,可以先将创建button的代码写出来 ,然后拖

快速创建UI控件的 方法 ,值得总结1

在平常写代码的时候相信大家 都会为每次创建 button或者其他的系统控件的 那么多行代码而烦恼 ,那么怎么能有个简单的方法来快速创建一个button呢.废话不多说,直接进入主题! 1.第一种方法  便利构造器 写一个UIButton的子类,添加一个便利构造器的方法,将所要传递的参数 都直接在调用便利构造器的方法时候 传递进去  ,然后返回一个  你想要的 button. 2.是用代码块 在xcode中 有一种可以快速创建代码的 功能 ,就是代码块,可以先将创建button的代码写出来 ,然后拖

C# Winform 跨线程更新UI控件常用方法总结(转)

出处:http://www.tuicool.com/articles/FNzURb 概述 C#Winform编程中,跨线程直接更新UI控件的做法是不正确的,会时常出现“线程间操作无效: 从不是创建控件的线程访问它”的异常.处理跨线程更新Winform UI控件常用的方法有4种: 1. 通过UI线程的SynchronizationContext的Post/Send方法更新: 2. 通过UI控件的Invoke/BegainInvoke方法更新: 3. 通过BackgroundWorker取代Thre

C# Winform 跨线程更新UI控件常用方法汇总

C# Winform 跨线程更新UI控件常用方法汇总 概述 C#Winform编程中,跨线程直接更新UI控件的做法是不正确的,会时常出现“线程间操作无效: 从不是创建控件的线程访问它”的异常.处理跨线程更新Winform UI控件常用的方法有4种:1. 通过UI线程的SynchronizationContext的Post/Send方法更新:2. 通过UI控件的Invoke/BeginInvoke方法更新: 3. 通过BackgroundWorker取代Thread执行异步操作:4. 通过设置窗体

C# WPF 使用委托修改UI控件

近段时间在自学WPF,是一个完全不懂WPF的菜鸟,对于在线程中修改UI控件使用委托做一个记录,给自已以后查询也给需要的参考: 界面只放一个RichTextBox,在窗体启动时开起两个线程,调用两个函数,每隔1秒写一次当前时间 一 界面XAML如下: <Window x:Class="WpfApplication1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation&qu

UI控件

在iOSAPP中,能在屏幕上显示的按钮.文本标签.文字输入框等等,都是UI控件. UIview: 苹果把所有UI控件的共同属性抽出来放在UIView中,即所有UI控件都是UIView的子类(不一定是直接子类). 注意:UIView继承自UIresponder,可以响应用户的操作. UIView的重要属性 frame:View的位置和宽高 bounce:相对于自己的左上角的位置和宽高 center:中心点的坐标 backgroundcolor:背景颜色 UIView的常用方法:UIView既可以显

JavaFX - UI控件 - 标签

  2标签(Label) 本章主要介绍如何使用标签(Label),该类位于JavaFX API的javafx.scene.control包中,用于显示一个文本元素. 接下来会介绍如何让文本元素自动换行来适应受限空间,添加一个图标,或使用视觉特效. 图2 - 1显示了标签的三种常见用法. 左边的标签是一个带图标的文本,中间的展示了旋转效果,右边的使用了自动换行设置. 图2 - 1 标签示例 这幅图显示了三个标签,他们被放在了同一行. 左边的标签有一个看起来像个放大镜的图标和一个"Searc