Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/swift/16.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Swift 使用嵌入的错误消息/用户反馈实现可组合谓词类型_Swift_Swiftui_Predicate - Fatal编程技术网

Swift 使用嵌入的错误消息/用户反馈实现可组合谓词类型

Swift 使用嵌入的错误消息/用户反馈实现可组合谓词类型,swift,swiftui,predicate,Swift,Swiftui,Predicate,我有一个基于简单闭包的NSPredicate的“快速”版本。这使得它可以组合,但我想找到一种实现错误消息的方法,以便在UI中向用户提供反馈 当我试图用逻辑AND组合两个谓词时,问题就出现了——在我当前的实现中(它使谓词非常简单),我找不到从组件谓词生成错误消息的有意义的方法。一个明显的解决方案是向谓词添加一个computed属性,该属性将重新计算谓词并返回一个错误(如果适用),但这似乎效率很低 我开始研究通过联合发布器公开错误消息,但这很快就失去了控制,看起来不必要的复杂。我的结论是,我现在看不

我有一个基于简单闭包的NSPredicate的“快速”版本。这使得它可以组合,但我想找到一种实现错误消息的方法,以便在UI中向用户提供反馈

当我试图用逻辑AND组合两个谓词时,问题就出现了——在我当前的实现中(它使谓词非常简单),我找不到从组件谓词生成错误消息的有意义的方法。一个明显的解决方案是向谓词添加一个computed属性,该属性将重新计算谓词并返回一个错误(如果适用),但这似乎效率很低

我开始研究通过联合发布器公开错误消息,但这很快就失去了控制,看起来不必要的复杂。我的结论是,我现在看不到树木的树木,可以做一点引导。代码库如下

谓词:

public struct Predicate<Target> {
    // MARK: Public roperties
    var matches: (Target) -> Bool
    var error: String

    // MARK: Init
    init(_ matcher: @escaping (Target) -> Bool, error: String = "") {
        self.matches = matcher
        self.error = error
    }

    // MARK: Factory methods
    static func required<LosslessStringComparabke: Collection>() -> Predicate<LosslessStringComparabke> {
        .init( { !$0.isEmpty }, error: "Required field")
    }

    static func characterCountMoreThan<LosslessStringComparable: Collection>(count: Int) -> Predicate<LosslessStringComparable> {
        .init({ $0.count >= count }, error: "Length must be at least \(count) characters")
    }

    static func characterCountLessThan<LosslessStringComparable: Collection>(count: Int) -> Predicate<LosslessStringComparable> {
        .init( { $0.count <= count }, error: "Length must be less than \(count) characters")
    }

    static func characterCountWithin<LosslessStringComparable: Collection>(range: Range<Int>) -> Predicate<LosslessStringComparable> {
        .init({ ($0.count >= range.lowerBound) && ($0.count <= range.upperBound) }, error: "Length must be between \(range.lowerBound) and \(range.upperBound) characters")
    }
}


// MARK: Overloads

// e.g. let uncompletedItems = list.items(matching: \.isCompleted == false)
func ==<T, V: Equatable>(lhs: KeyPath<T, V>, rhs: V) -> Predicate<T> {
    Predicate { $0[keyPath: lhs] == rhs }
}

// r.g. let uncompletedItems = list.items(matching: !\.isCompleted)
prefix func !<T>(rhs: KeyPath<T, Bool>) -> Predicate<T> {
    rhs == false
}


func ><T, V: Comparable>(lhs: KeyPath<T, V>, rhs: V) -> Predicate<T> {
    Predicate { $0[keyPath: lhs] > rhs }
}


func <<T, V: Comparable>(lhs: KeyPath<T, V>, rhs: V) -> Predicate<T> {
    //    Predicate { $0[keyPath: lhs] < rhs }
    Predicate({ $0[keyPath: lhs] < rhs }, error: "\(rhs) must be less than \(lhs)")
}


func &&<T>(lhs: Predicate<T>, rhs: Predicate<T>) -> Predicate<T> {
    return Predicate({ lhs.matches($0) && rhs.matches($0) }, error: "PLACEHOLDER: One predicate failed")
}

func ||<T>(lhs: Predicate<T>, rhs: Predicate<T>) -> Predicate<T> {
    Predicate({ lhs.matches($0) || rhs.matches($0) }, error: "PLACEHOLDER: Both predicates failed")
}
公共结构谓词{
//马克:公共财产
变量匹配:(目标)->Bool
变量错误:字符串
//MARK:Init
init(matcher:@escaping(Target)->Bool,错误:String=”“){
self.matches=匹配器
self.error=错误
}
//标记:工厂方法
static func required()->谓词{
.init({!$0.isEmpty},错误:“必填字段”)
}
静态func characterCountMoreThan(count:Int)->谓词{
.init({$0.count>=count},错误:“长度必须至少为\(count)个字符”)
}
静态func characterCountLessThan(count:Int)->谓词{
.init({$0.count谓词){
.init({($0.count>=range.lowerBound)&($0.count谓词){
谓词{$0[keyPath:lhs]==rhs}
}
//r.g.let uncompletedItems=列表项(匹配:!\.isCompleted)
前缀func!(rhs:KeyPath)->谓词{
rhs==假
}
func>(lhs:KeyPath,rhs:V)->谓词{
谓词{$0[keyPath:lhs]>rhs}
}
func谓词{
//谓词{$0[keyPath:lhs]谓词{
返回谓词({lhs.matches($0)&&rhs.matches($0)},错误:“占位符:一个谓词失败”)
}
func | |(lhs:谓词,rhs:谓词)->谓词{
谓词({lhs.matches($0)| | rhs.matches($0)},错误:“占位符:两个谓词都失败”)
}
验证器(使用谓词):

公共枚举验证错误:错误,CustomStringConvertible{ 大小写通用(字符串) 公共变量说明:字符串{ 切换自身{ case.generic(let error):返回错误 } } } 公共结构验证器{ 私有变量谓词:谓词 func validate(value:ValueType)->结果{ 开关谓词.matches(值){ 大小写正确: 返回。成功(值) 案例错误: return.failure(.generic(predicate.error))//TODO:占位符 } } init(谓词:谓词){ self.predicate=谓词 } } 验证程序结构由属性包装器使用:

@propertyWrapper
public class ValidateAndPublishOnMain<ValueType> where ValueType: LosslessStringConvertible { // Type constraint specifically for SwiftUI text controls
   @Published private var value: ValueType

    private var validator: Validator<ValueType>

    public var wrappedValue: ValueType {
        get { value }
        set { value = newValue }
    }

    // need to also force validation to execute when the textfield loses focus
    public var projectedValue: AnyPublisher<Result<ValueType, ValidationError>, Never> {
        return $value
            .receive(on: DispatchQueue.main)
            .map { value in
                self.validator.validate(value)
        }
        .eraseToAnyPublisher()
    }

    public init(wrappedValue initialValue: ValueType, predicate: Predicate<ValueType>) {
        self.value = initialValue
        self.validator = Validator(predicate: predicate)
    }
}
@propertyWrapper
public类ValidateAndPublishOnMain其中ValueType:LosslessStringConvertible{//Type约束专门用于SwiftUI文本控件
@已发布的私有var值:ValueType
私有var验证器:验证器
公共var wrappedValue:ValueType{
获取{value}
设置{value=newValue}
}
//当文本字段失去焦点时,还需要强制执行验证
public var projectedValue:AnyPublisher{
返回$value
.receive(在:DispatchQueue.main上)
.map{中的值
self.validator.validate(值)
}
.删除任何发布者()
}
public init(wrappedValue初始值:ValueType,谓词:谓词){
self.value=初始值
self.validator=验证器(谓词:谓词)
}
}
…最后,在SwiftUI中使用属性包装器(以及相关的视图模型)

public类ViewModel:observeObject{
@ValidateAndPublishOnMain(谓词:.required()&&.characterCountLessThan(计数:5))
var validatedData=“”{
willSet{objectWillChange.send()}
}
var errorMessage:String=“”
private var cancelables=Set()
init(){
setupBindings()
}
专用函数设置绑定(){
$validatedData
.map{中的值
开关量{
案例.成功:返回“”
case.failure(let error):返回error.description
}
}
.assign(发送至:\.errorMessage,打开:self)
.store(在:&可取消项中)
}
}
结构ContentView:View{
@ObservedObject var viewModel=viewModel()
@状态私有变量错误=“”
var body:一些观点{
VStack{
HStack{
文本(“标签”)
TextField(“此处的数据”,text:$viewModel.validatedData)
.textFieldStyle(RoundedBorderTextFieldStyle())
}.padding()
文本(“结果:\(viewModel.validatedData)”)
文本(“错误:\(viewModel.errorMessage)”)
}
奥纳佩尔先生{
self.viewModel.objectWillChange.send()//确保UI立即显示需求
}
}
}

产生歧义的主要原因是错误消息“一成不变”太早了。对于
&&
操作,在计算表达式之前,您不知道错误消息

因此,您不应该存储
error
属性。相反,只在
匹配时输出错误消息,即作为其返回值。当然,您还需要处理没有错误消息的成功状态

Swift提供了多种建模方法-您可以返回
字符串@propertyWrapper
public class ValidateAndPublishOnMain<ValueType> where ValueType: LosslessStringConvertible { // Type constraint specifically for SwiftUI text controls
   @Published private var value: ValueType

    private var validator: Validator<ValueType>

    public var wrappedValue: ValueType {
        get { value }
        set { value = newValue }
    }

    // need to also force validation to execute when the textfield loses focus
    public var projectedValue: AnyPublisher<Result<ValueType, ValidationError>, Never> {
        return $value
            .receive(on: DispatchQueue.main)
            .map { value in
                self.validator.validate(value)
        }
        .eraseToAnyPublisher()
    }

    public init(wrappedValue initialValue: ValueType, predicate: Predicate<ValueType>) {
        self.value = initialValue
        self.validator = Validator(predicate: predicate)
    }
}
public class ViewModel: ObservableObject {
    @ValidateAndPublishOnMain(predicate: .required() && .characterCountLessThan(count: 5))
    var validatedData = "" {
        willSet { objectWillChange.send() }
    }

    var errorMessage: String = ""
    private var cancellables = Set<AnyCancellable>()

    init() {
        setupBindings()
    }

    private func setupBindings() {
        $validatedData
            .map { value in
                switch value {
                case .success: return ""
                case .failure(let error): return error.description
                }
        }
        .assign(to: \.errorMessage, on: self)
        .store(in: &cancellables)
    }
}

struct ContentView: View {
    @ObservedObject var viewModel = ViewModel()
    @State private var error = ""

    var body: some View {
        VStack {
            HStack {
                Text("Label")
                TextField("Data here", text: $viewModel.validatedData)
                    .textFieldStyle(RoundedBorderTextFieldStyle())

            }.padding()

            Text("Result: \(viewModel.validatedData)")
            Text("Errors: \(viewModel.errorMessage)")
        }
        .onAppear {
            self.viewModel.objectWillChange.send() // ensures UI shows requirements immediately
        }
    }
}
public struct ValidationError: Error {
    let message: String
}

public struct Predicate<Target> {
    var matches: (Target) -> Result<(), ValidationError>

    // MARK: Factory methods
    static func required<T: Collection>() -> Predicate<T> {
        .init { !$0.isEmpty ? .success(()) : .failure(ValidationError(message: "Required field")) }
    }

    static func characterCountMoreThan<T: StringProtocol>(count: Int) -> Predicate<T> {
        .init { $0.count > count ? .success(()) : .failure(ValidationError(message: "Length must be more than \(count) characters")) }
    }

    static func characterCountLessThan<T: StringProtocol>(count: Int) -> Predicate<T> {
        .init { $0.count < count ? .success(()) : .failure(ValidationError(message: "Length must be less than \(count) characters")) }
    }

    static func characterCountWithin<T: StringProtocol>(range: Range<Int>) -> Predicate<T> {
        .init {
            ($0.count >= range.lowerBound) && ($0.count <= range.upperBound) ?
                .success(()) :
                .failure(ValidationError(message: "Length must be between \(range.lowerBound) and \(range.upperBound) characters")) }
    }
}

func ==<T, V: Equatable>(lhs: KeyPath<T, V>, rhs: V) -> Predicate<T> {
    Predicate {
        $0[keyPath: lhs] == rhs ?
            .success(()) :
            .failure(ValidationError(message: "Must equal \(rhs)"))
    }
}

// r.g. let uncompletedItems = list.items(matching: !\.isCompleted)
prefix func !<T>(rhs: KeyPath<T, Bool>) -> Predicate<T> {
    rhs == false
}

func ><T, V: Comparable>(lhs: KeyPath<T, V>, rhs: V) -> Predicate<T> {
    Predicate {
        $0[keyPath: lhs] > rhs ?
            .success(()) :
        .failure(ValidationError(message: "Must be greater than \(rhs)"))
    }
}

func <<T, V: Comparable>(lhs: KeyPath<T, V>, rhs: V) -> Predicate<T> {
    Predicate {
        $0[keyPath: lhs] < rhs ?
            .success(()) :
        .failure(ValidationError(message: "Must be less than \(rhs)"))
    }
}


func ||<T>(lhs: Predicate<T>, rhs: Predicate<T>) -> Predicate<T> {
    // short-circuiting version, needs a nested switch
//    Predicate {
//        target in
//        switch lhs.matches(target) {
//        case .success:
//            return .success(())
//        case .failure(let leftError):
//            switch rhs.matches(target) {
//            case .success:
//                return .success(())
//            case .failure(let rightError):
//                return .failure(ValidationError(message: "\(leftError.message) AND \(rightError.message)"))
//            }
//        }
//    }

    // without a nested switch, not short-circuiting
    Predicate {
        target in
        switch (lhs.matches(target), rhs.matches(target)) {
        case (.success, .success), (.success, .failure), (.failure, .success):
            return .success(())
        case (.failure(let leftError), .failure(let rightError)):
            return .failure(ValidationError(message: "\(leftError.message) AND \(rightError.message)"))
        }
    }
}

func &&<T>(lhs: Predicate<T>, rhs: Predicate<T>) -> Predicate<T> {
    Predicate {
        target in
        switch (lhs.matches(target), rhs.matches(target)) {
        case (.success, .success):
            return .success(())
        case (.success, let rightFail):
            return rightFail
        case (let leftFail, .success):
            return leftFail
        case (.failure(let leftError), .failure(let rightError)):
            return .failure(ValidationError(message: "\(leftError.message) AND \(rightError.message)"))
        }
    }
}

@propertyWrapper
public class ValidateAndPublishOnMain<ValueType> where ValueType: LosslessStringConvertible { // Type constraint specifically for SwiftUI text controls
   @Published private var value: ValueType

    private var validator: Predicate<ValueType>

    public var wrappedValue: ValueType {
        get { value }
        set { value = newValue }
    }

    // need to also force validation to execute when the textfield loses focus
    public var projectedValue: AnyPublisher<Result<ValueType, ValidationError>, Never> {
        return $value
            .receive(on: DispatchQueue.main)
            .map { value in
                // mapped the Result' Success type
                self.validator.matches(value).map { _ in value }
        }
        .eraseToAnyPublisher()
    }

    public init(wrappedValue initialValue: ValueType, predicate: Predicate<ValueType>) {
        self.value = initialValue
        self.validator = predicate
    }
}