Cocoa Binding 实用教程

首先说这不是一个新技术,它很老,老到几乎没人提起它。

这是苹果 MVC 模式下的产物,最早在没有 iPhone 的时候就已经诞生了,它是用来配合 Xcode 图形化设置界面用的——比如 NIB,当然,现在已经变成 XIB了,哦,还有 Storyboard。

现在如果说起要 bind 一个 Storyboard 中的对象到代码中,你可能查到的都是这样的:

在左侧栏点击鼠标右键打开小窗口,然后【拉线】连接代码中的声明与实际的 UIButton……

这样在程序加载这个 Storyboard 时,你图形化创建的 Button 就自动连接到了代码的声明中,比如:

没错,通常来说我们是这么用的,但如果我这是一个“设置”界面,里边有大量的简单控件,但要根据用户当前设置状态来改变和显示(这是个很常见的情景,毕竟哪个 App 没个偏好设置呢?),大量的 @IBOutlet@IBAction 可能并不是一个好的选择,因为除了这些,你还需要在初始化代码中增加大量的配置读取代码,状态判断,然后再去更改对应控件的状态,如果选项多的话,那基本就是灾难了。

就拿落格输入法的设置界面来说,每一页都有很多的按钮控件
就拿落格输入法的设置界面来说,每一页都有很多的按钮控件

Cocoa Binding 救你命

打开 Xcode ,加载一个 Xib,选中一个控件,在右侧栏你会看到常见的属性设置界面:

Xcode 中常见的属性设置栏
Xcode 中常见的属性设置栏

你可能经常使用前边几个功能:

通常 Xcode 中常用的功能选项
通常 Xcode 中常用的功能选项

但,这个功能你肯定没用过:

Cocoa Binding 设置界面
Cocoa Binding 设置界面

如果你去网上查找,那几乎得不到什么有用的介绍——因为它很古老了。得益于 Objc 的 MVC 设计风格,实际上每一个控件都可以直接监听你对象中的变量,并自动根据它的变化来改变自身显示的,你完全不需要去初始化那个对象,也不用根据用户当前的设置来设定这个按钮是可用还是不可用,一切其实都可以是自动的,你需要提供的——不过是一个变量而已。

不过问题来了,现在已经都在用 Swift 了,怎么才能让它绑定到 Swift 代码中的变量呢?如果你声明一个变量,并用这个界面来进行绑定,那么就会遇到运行时错误:

这是由于 Swift 默认不向 Objc 暴露变量名称导致的,如果任何 #selector()  调用方法一样,我们在对应的变量前加上 @objc dynamic ,比如这样 @objc dynamic var mySetting = false ,这样,就可以绑定成功了。

注意,这里我们除了常见的 @objc 外,还追加了 dynamic 修饰,如果只使用前者,则可能在第一次调用时失败, Swift 依旧会将你声明的变量优化成静态,这样 Cocoa Binding 就不能动态监听我们的绑定了。

(取决于你的编译优化,为了保证无论如何绑定都能生效,还是应该加上 dynamic 修饰)

在绑定界面,我们选中一个 Button,比如我这个按钮控件是有选中状态的那种 ☑️,所以它有开和关两种状态,于是我就可以把它的 value 进行绑定,这里要记得先设置好 Xib 的 File's Owner 一般这个默认是空的,设置好后,我们就可以选择绑定到 File's Owner 了:

绑定对象实例变量到 UI 界面
绑定对象实例变量到 UI 界面

这里要使用 KeyPath 进行设置,实际上就是你变量的名字即可。现在这个按钮的状态就会自动根据变量的值进行改变了。完全不需要任何初始化设置。

额外的,除了按钮对象,你还可以绑定 Label,显然,它的 value 就是字符串对象了。值得一提的是,如果你绑定一个数组控件……比如 NSPopUpButton 这种,它实际上是有多个数据,那么你也可以在这个设置界面的下方找到 selectedIndex 这样的状态进行绑定,值类型就是我们熟悉的 Int ,如果你直接绑定 value,那值其实是一个比较复杂的 NSArrayController。

当然,如你所见,你还可以方便地绑定一个空间的 Enabled, isHidden 等等状态,但凡是能设置的状态,几乎都能和变量进行绑定,根据情况自动发生改变——再也不用繁琐的代码来操作界面了!

进阶:ValueTransformer

设置绑定的时候,会有一个默认留空的选项, ValueTransformer ,它可以将你绑定的值进行一定的处理,比如默认的,将布尔值进行反转,判断空为 true  或者 false ,除此之外,我们还可以自定义如何对值进行处理。

比如我的设置选项有 0 1 2 三个状态,但按钮选中与否是两个,我需要将它映射到 0 和 2 上,默认的 Transformer 就无能为力了。

首先,我们要继承一个 ValueTransformer ,这样就能自定义处理流程:

其中 transformedValue(_ value: Any?)-> Any? 返回的是控件需要的值,比如我的按钮,我就设置 return a == 2 这样当且仅当值为 2 时按钮状态为启用。

allowsReverseTransformation() -> Bool 规定了这个 ValueTransformer  是否能逆向转换,默认是 false ,这里我需要逆向,因为按钮选定时应该给变量设置为 2 而不是 1 ,所以我要使用 reverseTransformedValue(_ value: Any?) -> Any? 来将按钮控件传入的值转换成变量需要的值。

写好后我们需要将这个自定义的 ValueTransformer  进行注册,否则 Swift 是找不到这个对象的:

我们在任何初始化界面之前的地方注册即可,注意我直接给它起名为 "ValueTransformer2" ,然后就可以在 Xcode 的 Binding 界面将 ValueTransformer 写成这个 ValueTransformer2 了,这里我故意将名称写的和类名称一致,你也可以写成其他不对应的名字。


额外地,如果你用了自己的配置管理实例,那么也可以将绑定的变量写为计算属性,这是完全没有问题的,不过值得一提的是,绑定变量的控件,是依靠监控对应变量的 didSet 来获取通知的,这是唯一触发界面更新的方式,所以,如果你使用了计算属性,那么更新了后台存储后,记得也要触发一下计算属性的 didSet ,比如

 

本文由 落格博客 原创撰写:落格博客 » Cocoa Binding 实用教程

转载请保留出处和原文链接:https://www.logcg.com/archives/3456.html

作者:落格博客

如非声明,本人所著文章均为原创手打,转载请注明本页面链接和我的名字。

发表评论

邮箱地址不会被公开。 必填项已用*标注