SwiftUI可选TextField

Bra*_*ley 11 swiftui

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

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

    var body: some View {
        TextField($test)
    }
}
Run Code Online (Sandbox Code Playgroud)

产生以下错误:

无法将类型'Binding <String?>'的值转换为预期的参数类型'Binding <String>'

有没有办法解决?在数据模型中使用Optionals是一种非常常见的模式-实际上,这是Core Data中的默认设置,因此SwiftUI不支持它们似乎很奇怪

Jon*_*an. 50

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

func ??<T>(lhs: Binding<Optional<T>>, rhs: T) -> Binding<T> {
    Binding(
        get: { lhs.wrappedValue ?? rhs },
        set: { lhs.wrappedValue = $0 }
    )
}
Run Code Online (Sandbox Code Playgroud)

这将创建一个 Binding,如果它不是 nil,则返回运算符值的左侧,否则从右侧返回默认值。

设置时只设置 lhs 值,而忽略与右侧有关的任何事情。

它可以像这样使用:

TextField("", text: $test ?? "default value")
Run Code Online (Sandbox Code Playgroud)

  • 这绝对是天才!!!很棒的解决方案!我不明白,为什么 SwiftUI 不支持开箱即用...... (4认同)
  • 这个函数写在哪里呢? (2认同)
  • @johndoe 只是作为一个全局函数。这只是一个运算符重载 (2认同)

Fré*_*dda 8

确实,目前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)
    }
}
Run Code Online (Sandbox Code Playgroud)

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


Bra*_*ley 6

最终,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
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这使您可以保留可选内容,同时使其与Bindings兼容:

TextField($test.bound)
Run Code Online (Sandbox Code Playgroud)

  • 这太棒了,因为它完全有效!我觉得必须有更好的方法,因为“Binding”有一个[“可选初始化程序”](https://developer.apple.com/documentation/swiftui/binding/3115381-init)。我只是不知道如何将它与 Swift 速记一起使用。 (2认同)

and*_*der 5

我更喜欢@Jonathon提供的答案Optional因为它简单而优雅,并且为编码器提供了当is .none(= nil) 和 not 时的原位基本情况.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 }
            }
        )
    }
}
Run Code Online (Sandbox Code Playgroud)

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

TextField("", text: Binding($test, replacingNilWith: String()))
Run Code Online (Sandbox Code Playgroud)

或者

TextField("", text: Binding($test, replacingNilWith: ""))
Run Code Online (Sandbox Code Playgroud)