4

Cocoa Binding practical tutorial

 2 years ago
source link: https://www.logcg.com/archives/3456.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
最近更新:3rd 8月, 2021

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

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

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

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

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

@IBOutlet var myBox:NSBox!
@IBOutlet var myButton:UIButton!

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

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

Cocoa Binding 救你命

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

Xcode 中常见的属性设置栏

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

通常 Xcode 中常用的功能选项

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

Cocoa Binding 设置界面

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

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

this class is not key value coding-compliant for the key xxx

这是由于 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 界面

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

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

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

进阶:ValueTransformer

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

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

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

class ValueTransformer2:ValueTransformer {
    override func transformedValue(_ value: Any?) -> Any? {
        guard let a = value as? Int else {return false}
        return a == 2
    class override func allowsReverseTransformation() -> Bool {
        return true
    override func reverseTransformedValue(_ value: Any?) -> Any? {
        guard let a = value as? Bool else {return false}
        return a ? 2 : 0

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

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

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

ValueTransformer.setValueTransformer(ValueTransformer2(), forName: NSValueTransformerName("ValueTransformer2"))

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


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

@objc var myValue:Int {
get {your code...}
set {do nothing}
myValue = 0

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

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK