如何重新渲染 UI 以响应埋在嵌套 class 中的计算 属性?

How to re-render UI in response to computed property buried in a nested class?

我不清楚如何将以下计算的 属性 的输出合并到 UI。

var isComplete: Bool {
    Set([.givenName, .familyName]).isSubset(of: elements)
}

我基本上希望在上述更改时更新用户界面。我将如何使用 Combine 来做到这一点?

响应式编程要求我现在向后思考,我在思考模型 <<< UI 而不是模型 >>> UI.

时遇到了麻烦

这是上下文中的代码。

struct EditPersonView: View {
    
    let model: ViewModel
            
    private var captionView: some View {
        HStack {
        /*
          stuff
        */
            if submitted && model.name.isComplete {
                Spacer()
                Text("select".localizedCapitalized) + Text(" ") + Text("save") + Text(" ") + Text("")
            }
        }
    }

    var body: some View {
    /*
    stuff - including captionView
    */
    }
}

extension EditPersonView {
    
    final class ViewModel {
        
        let name: PersonName
        
        init(person: Person) {
            self.name = PersonName(for: person)
        }
    }
}

extension EditPersonView.ViewModel {
    
    final class PersonName {
        
        let person: Person
        
        private let formatter = PersonNameComponentsFormatter()
        
        init(for person: Person) {
            self.person = person
        }
        
        var text: String {
            get { person.name ?? "" }
            set { person.name = newValue }
        }
        
        private var components: PersonNameComponents? {
            formatter.personNameComponents(from: text)
        }
        
        var givenName: String? {
            components?.givenName
        }
        
        var familyName: String? {
            components?.familyName
        }
        
        private func isValid(component: String?) -> Bool {
            if let name = component, name.count > 1 {
                return true
            }
            return false
        }
        
        var elements: Set<Elements> {
            var collection = Set<Elements>()
            if isValid(component: givenName) { collection.insert(.givenName) }
            if isValid(component: familyName) { collection.insert(.familyName) }
            return collection
        }
        
        var isComplete: Bool {
            Set([.givenName, .familyName]).isSubset(of: elements)
        }
    }
}

extension EditPersonView.ViewModel.PersonName {
    
    enum Elements {
        case givenName, familyName
    }
}

下面是我想出的。

序列的根是 textPublisher。这从发送到 text.

的值开始序列

didSet将文本发送到序列中,并像原始代码一样将其保存在人名中。

isComplete 成为发布者,根据组件是否有效发送 truefalsemap 运算符链中的每个运算符都通过原始代码中的一个计算步骤获取值。你可以很容易地将它减少到一个 map 我想。 (或者将计算过滤到具有有意义名称的函数中,并将函数替换为闭包)

外部 Subscriber 可以订阅 isComplete 并在发出 true 值时响应。

final class PersonName {
    var person: Person

    private let formatter = PersonNameComponentsFormatter()

    let textPublisher = PassthroughSubject<String, Never>()
    var text: String {
        get { person.name ?? "" }
        set { textPublisher.send(newValue); person.name = newValue }
    }

    var isComplete : AnyPublisher<Bool, Never>!

    init(for person: Person) {
        self.person = person

        isComplete = textPublisher
            .map{ self.formatter.personNameComponents(from: [=10=]) }
            .map{ (components: PersonNameComponents?) -> Set<Elements> in
                var collection = Set<Elements>()

                if let components = components {
                    if self.isValid(component: components.givenName) { collection.insert(.givenName) }
                    if self.isValid(component: components.familyName) { collection.insert(.familyName) }
                }

                return collection
            }
            .map { Set([Elements.givenName, Elements.familyName]).isSubset(of: [=10=]) }
            .eraseToAnyPublisher()
    }

    private func isValid(component: String?) -> Bool {
        if let name = component, name.count > 1 {
            return true
        }
        return false
    }
}