[SwiftUI 100 天] 在 SwiftUI 中创建自定义绑定

1,114 阅读2分钟
译自 www.hackingwithswift.com/books/ios-s…

在 SwiftUI 中创建自定义绑定

由于 SwiftUI 给属性包装器发送绑定更新的机制,给属性包装器赋予属性观察者是不管用的。也就是说,尽管模糊半径改变了,打印语句也不会被调用:

struct ContentView: View {
    @State private var blurAmount: CGFloat = 0 {
        didSet {
            print("New value is \(blurAmount)")
        }
    }

    var body: some View {
        VStack {
            Text("Hello, World!")
                .blur(radius: blurAmount)

            Slider(value: $blurAmount, in: 0...20)
        }
    }
}

为了解决这个问题我们需要创建一个自定义绑定 —— 我们需要直接使用 Binding 结构体,它能让我们在值被读取或者写入时提供自己的代码。

在我们的代码中,我们要一个 Binding 被读取时返回 blurAmount 的值,而被写入时用新值改变 blurAmount 同时也打印出新值,以便我们能看到这个变化过程。此外,不管我们是读取还是写入,都是在对 blurAmount 属性进行操作,而 Swift 是不允许我们创建读取其他属性的属性,因为我们要读取的属性很有可能还没被初始化。

总结一下,我们需要创建一个自定义 Binding,以转嫁 blurAmount,并且在设置它的值时打印消息。另外,我们不能把这个绑定存储为视图的属性,因为从一个属性中读取另一个是不允许的。

因此,我们需要把代码放进视图的 body 属性中,像这样:

struct ContentView: View {
    @State private var blurAmount: CGFloat = 0

    var body: some View {
        let blur = Binding<CGFloat>(
            get: {
                self.blurAmount
            },
            set: {
                self.blurAmount = $0
                print("New value is \(self.blurAmount)")
            }
        )

        return VStack {
            Text("Hello, World!")
                .blur(radius: blurAmount)

            Slider(value: blur, in: 0...20)
        }
    }
}

在我们深入绑定之前,注意到一个细节:我们仍然是使用 @State private var 来声明 blurAmount 属性,用 blur(radius: blurRadius) 作为文本视图的 modifier。

改变之处在于我们声明 slider 里绑定:我们不再使用 $blurAmount,而是用 blur。这是由于,使用 $ 是我们从某个状态中获得双向绑定的方式,而我们现在是直接创建绑定,所以不需要它了。

现在让我们来绑定本身,你应该也能从我们使用它的方式上看出端倪,基本的构造器是像下面这样:

init(get: @escaping () -> Value, set: @escaping (Value) -> Void)

依然通过 Cmd+Shift+O 在 SwiftUI 的生成接口中查找 “Binding”。拆解一下,构造器接收两个闭包:一个不接收参数,返回一个值的 getter,以及一个接收一个值,什么也不返回的 setter。Binding 使用的是泛型,因此 Value 实际上是一个占位符,我们可以往里存储任何类型 —— 比如例子中 blur 绑定的 CGFloatgetset 闭包都被标记为 @escaping,表示 Binding 结构体会存储闭包,以便之后使用。

所有这些表明你可以在闭包里做任何事情:调用方法,运行算法计算正确的值,或者使用随机的值 —— 只要你返回一个值。因此,假如你希望在每次值改变时把它写入 UserDefaults,那么一个 Bindingset 闭包能完美地满足你的需求。


我的公众号 这里有Swift及计算机编程的相关文章,以及优秀国外文章翻译,欢迎关注~