Validation 用户输入的SwiftUI验证和否决

Validation 用户输入的SwiftUI验证和否决,validation,swiftui,combine,Validation,Swiftui,Combine,我希望在SwiftUI中实现一个通用的验证/否决循环——这类事情在“单一真相来源”框架中应该非常简单 简言之,我想: 有一个通用控件(比如说一个TextField) 对该控件的更新应用验证/否决(例如,用户键入文本) 将预期的更改传播到验证器中,在某处更新绑定源对象(理想情况下,是视图中的@State成员) 将该值反馈到控件以进行显示 似乎对于所有“单一真相来源”的说法,苹果都是在撒谎——在这个链条中注入验证阶段似乎很困难,尤其是在不破坏观点封装的情况下 注意,我并不想特别解决这个问题-我正

我希望在SwiftUI中实现一个通用的验证/否决循环——这类事情在“单一真相来源”框架中应该非常简单

简言之,我想:

  • 有一个通用控件(比如说一个
    TextField
  • 对该控件的更新应用验证/否决(例如,用户键入文本)
  • 将预期的更改传播到验证器中,在某处更新
    绑定
    源对象(理想情况下,是
    视图中的
    @State
    成员)
  • 将该值反馈到控件以进行显示
似乎对于所有“单一真相来源”的说法,苹果都是在撒谎——在这个链条中注入验证阶段似乎很困难,尤其是在不破坏观点封装的情况下

注意,我并不想特别解决这个问题-我正在寻找一种模式来实现(例如:用
Bool
Toggle
替换
String
TextField

下面的代码显示了执行上述循环的最佳尝试

class ValidatedValue<T>: ObservableObject {

    let objectWillChange = ObservableObjectPublisher()

    var validator: (T, T)->T

    var value: T {
        get {
            _value
        }
        set {
            _value = validator(_value, newValue)
            objectWillChange.send()
        }
    }

    /// Backing value for the observable
    var _value: T

    init(_ value: T, validator: @escaping (T, T)->T) {
        self._value = value
        self.validator = validator
    }
}

struct MustHaveDTextField: View {

    @ObservedObject var editingValue: ValidatedValue<String>

    public var body: some View {
        return TextField(
            "Must have a d",
            text: $editingValue.value
    }
}
如果字符串输入不包含“d”,则这种方法会阻止您修改字符串输入。但是,

  • 光标状态仍会在文本控件上移动,超过验证点
  • 它公开了应该完全是内部状态的内容,并要求从父级或通过
    环境对象
    (如果您使用
    列表
    来执行此操作,那么
要么我遗漏了一些关键的东西,要么我采取了错误的方法,要么苹果说的不是苹果做的


在循环过程中修改内部状态,就像做了什么或不好一样-它们修改视图循环内的状态,其中XCode将其标记为
未定义的行为
。也有一个类似的解决方案,但同样需要将验证逻辑放在视图之外——IMHO它应该是自包含的

我不认为这是一种正确的方法,但我处理这一问题的一种方法是将已处理的验证规则的结果(在我的例子中是在组合管道中编码的)表示为结果属性:

  • validationMessages:String[]
  • isEverythingOK:Boolean
我通过为用户可用的字段输入公开
@Published
属性来连接验证,在模型上,我将每个属性与一个组合主题配对,使用属性上的
didSet{}
闭包发送更新。然后,验证规则都包含在模型上的联合收割机管线中,只显示结果

关于我如何在中处理它,有一些示例代码,可以在和的Github上找到

例如,我将尝试在这里包括相关的位。请注意,在这个示例中,我有意为SwiftUI视图公开一个发布者,但实际上只是为了表明这是可能的,而不是为了解决这个特定的解决方案

在实践中,我发现,当表单未经验证时,形式化或确切地知道您想要显示什么对我开发解决方案的方式产生了巨大的影响

<代码>导入基础 进口联合收割机 类ReactiveFormModel:ObservableObject{ @已发布的var firstEntry:String=“”{ 迪塞特{ firstEntryPublisher.send(self.firstEntry) } } private let firstEntryPublisher=CurrentValueSubject(“”) @已发布的var secondEntry:String=“”{ 迪塞特{ secondEntryPublisher.send(self.secondEntry) } } private let secondEntryPublisher=CurrentValueSubject(“”) @已发布的var validationMessages=[String]() 私有变量CancelableSet:Set=[] var submitAllowed:AnyPublisher init(){ 让validationPipeline=publisher.CombineTest(firstEntryPublisher,secondEntryPublisher) .map{(arg)->[String]位于 var diagMsgs=[String]() let(value,value_repeat)=arg 如果!(值_repeat==值){ append(“字段的值必须匹配。”) } if(value.count<5 | | value_repeat.count<5){ append(“请输入至少5个字符的值”) } 返回图 } submitAllowed=validationPipeline .map{stringArray在 返回stringArray.count<1 } .删除任何发布者() 让ux=validationPipeline .assign(到:\.validationMessages,在:self上) .store(位于:&可取消集) } }
ValidatedValue(
    "oddity has a d",
    validator: { current, new in
        if new.contains("d") {
            return new
        }
        else {
            return current
        }
    }
)