运用 Binding 的时分咱们经常会遇到需求绑定一个 Optional value 的状况。比如一个用于输入用户名的 TextField,假如是老用户就直接显示已有的用户名,新用户则为空。

struct BindingOptional: View {
    @State var userName: String? = nil
    var body: some View {
        TextField("UserName", text: $userName)
    }
}

这样写毫无疑问是要报错的,因为 Binding 的目标有必要是一个确定的值。

SwiftUI Tips: 如何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")
}