SwiftUI 可选文本字段

SwiftUI Optional TextField

SwiftUI 文本字段可以与可选绑定一起使用吗?当前此代码:

struct SOTestView : View {
    @State var test: String? = "Test"

    var body: some View {
        TextField($test)
    }
}

产生以下错误:

Cannot convert value of type 'Binding< String?>' to expected argument type 'Binding< String>'

有什么办法解决这个问题吗?在数据模型中使用 Optionals 是一种非常常见的模式——事实上它是 Core Data 中的默认模式,所以 SwiftUI 不支持它们似乎很奇怪

最终 API 不允许这样做 - 但有一个非常简单且通用的解决方法:

extension Optional where Wrapped == String {
    var _bound: String? {
        get {
            return self
        }
        set {
            self = newValue
        }
    }
    public var bound: String {
        get {
            return _bound ?? ""
        }
        set {
            _bound = newValue.isEmpty ? nil : newValue
        }
    }
}

这允许您保留可选的同时使其与绑定兼容:

TextField($test.bound)

是的,目前 TextField 在 SwiftUI 中只能绑定到 String 个变量,不能 String?。 但是您始终可以像这样定义自己的 Binding

import SwiftUI

struct SOTest: View {
    @State var text: String?

    var textBinding: Binding<String> {
        Binding<String>(
            get: {
                return self.text ?? ""
        },
            set: { newString in
                self.text = newString
        })
    }

    var body: some View {
        TextField("Enter a string", text: textBinding)
    }
}

基本上,您将 TextField 文本值绑定到这个新的 Binding<String> 绑定,绑定将它重定向到您的 String? @State 变量。

您可以添加此运算符重载,然后它就像不是 Binding 一样自然地工作。

func ??<T>(lhs: Binding<Optional<T>>, rhs: T) -> Binding<T> {
    Binding(
        get: { lhs.wrappedValue ?? rhs },
        set: { lhs.wrappedValue = [=10=] }
    )
}

这将创建一个绑定,如果运算符的值不为零,则 returns 左侧,否则 returns 右侧的默认值。

设置时只设置lhs值,忽略与右手边有关的任何内容。

可以这样使用:

TextField("", text: $test ?? "default value")

试试这个对我有用的可重用功能

@State private var name: String? = nil
private func optionalBinding<T>(val: Binding<T?>, defaultVal: T)-> Binding<T>{
    Binding<T>(
        get: {
            return val.wrappedValue ?? defaultVal
        },
        set: { newVal in
            val.wrappedValue = newVal
        }
    )
}
// Usage
TextField("", text: optionalBinding(val: $name, defaultVal: ""))

我更喜欢 provided by @Jonathon.,因为它简单而优雅,并且在 Optional.none(= nil)而不是.some.

但是我觉得值得在这里加上我的两分钱。我通过阅读 Jim Dovey 在 SwiftUI Bindings with Core Data 上的博客学习了这项技术。它与@Jonathon 提供的答案基本相同。但确实包含一个很好的模式,可以为许多不同的数据类型复制。

首先在 Binding

上创建一个扩展
public extension Binding where Value: Equatable {
    init(_ source: Binding<Value?>, replacingNilWith nilProxy: Value) {
        self.init(
            get: { source.wrappedValue ?? nilProxy },
            set: { newValue in
                if newValue == nilProxy { source.wrappedValue = nil }
                else { source.wrappedValue = newValue }
            }
        )
    }
}

然后像这样在你的代码中使用...

TextField("", text: Binding($test, replacingNilWith: String()))

TextField("", text: Binding($test, replacingNilWith: ""))