使用 Binding 的时候我们经常会遇到需要绑定一个 Optional value 的情况。比如一个用于输入用户名的 TextField,如果是老用户就直接显示已有的用户名,新用户则为空。
struct BindingOptional: View {
@State var userName: String? = nil
var body: some View {
TextField("UserName", text: $userName)
}
}
这样写毫无疑问是要报错的,因为 Binding 的目标必须是一个确定的值。
但是如果特意再声明一个 @State 做为中继,则繁琐很多。如果这样做代码大概要这么写:
struct BindingOptional: View {
@State var userName: String? = nil
@State var userNameTemp: String = "none"
var body: some View {
TextField("UserName", text: $userNameTemp)
.onChange(of: userNameTemp) {
self.userName = userNameTemp
}
}
}
因此找到一种方式可以便利针对 optional value的情况提供一个默认值很有必要。
自定义一个 Binding
@Binding 本质上是一个 property wrapper,最底层的思路就是我们把 Binding<T?> 封装成 Binding<T> 返回。
实现的代码如下:
struct BindingOptional: View {
@State var userName: String? = nil
private var userNameTemp: Binding<String> {
Binding(get: {
"none"
}, set: { newValue in
self.userName = newValue
})
}
var body: some View {
TextField("UserName", text: userNameTemp)
}
}
我们可以在 get 闭包中返回默认值。
更进一步,封装成方法
但是这样写还是有点繁琐,我们可以把这个封装的过程定义为 Binding 的一个扩展方法。
extension Binding {
func defaultValue<T>(_ value: T) -> Binding<T> where Value == Optional<T> {
Binding<T> {
wrappedValue ?? value
} set: {
wrappedValue = $0
}
}
}
我们就可以这样调用:
var body: some View {
TextField("UserName", text: $userName.defaultValue("none"))
}
考虑到我们最常使用的是字符串,因此我们可以针对字符串封装一个默认值是空字符的方法。
extension Binding where Value == Optional<String> {
public var orEmpty: Binding<String> {
Binding<String> {
wrappedValue ?? ""
} set: {
wrappedValue = $0
}
}
}
这样我们使用的时候就会更简便一些:
var body: some View {
TextField("UserName", text: $userName.orEmpty)
}
自定义运算符
除了定义成一个方法,也可以通过自定义运算符实现。因为 ?? 运算符的语义本来就代表提供默认值。如果想要更简洁一点把这个方法定义成 binding 的运算符也很合适。
extension Binding {
static func ?? <T>(optional: Self, defaultValue: T) -> Binding<T> where Value == Optional<T> {
Binding<T>(
get: { optional.wrappedValue ?? defaultValue },
set: { optional.wrappedValue = $0 }
)
}
}
我们就可以这样调用:
var body: some View {
TextField("UserName", text: $userName ?? "none")
}