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: blurAmount)
作为文本视图的修饰符。
更改的一个地方是我们在滑块中声明绑定的方式:我们不在使用$blurAmount
,而是直接使用blur
。这是因为使用美元符号可以使我们从某个状态属性获得双向绑定,但是现在我们已经直接创建了绑定,因此不再需要它。
好的,现在让我们来看一下绑定本身。您应该可以从我们的使用方式中看出来,Binding
的基本初始化器如下所示:
init(get: @escaping () -> Value, set: @escaping (Value) -> Void)
您可以通过按 Cmd + Shift + O 并在已生成的SwiftUI界面中查找“Binding”来找到它。详细地讲,它告诉我们初始化程序采用两个闭包:一个不带参数并返回值的getter
,和一个带值但不返回任何值的setter
。绑定使用泛型,因此Value
实际上是我们存储在内部的所有内容的占位符——对于我们的模糊绑定而言是CGFloat
。get
和set
闭包都标记为@escaping
,这意味着Binding
结构体存储它们供以后使用。
这意味着您可以在这些闭包内做任何您想做的事情:可以调用方法,运行算法以找出要使用的正确值,甚至只是使用随机值——没关系,只要您从get
返回值。因此,如果要确保每次更改值时都更新UserDefaults
,则Binding
的set
闭包是完美的。