为什么我的视图没有在 SwiftUI 中呈现,即使 Publisher 发出了一个值

Why my views are not Rendered in SwiftUI even though the Publisher emits a value

我在 swiftUI 中有一个用户名 textField。我正在尝试在发布者的帮助下验证输入。

这是我的代码:

View

struct UserView: View {
    @StateObject private var userViewModel = UserViewModel()

    init(){
        UITextField.appearance().semanticContentAttribute = .forceRightToLeft
        UITextField.appearance().keyboardAppearance = .dark
    
    }

    var body: some View {
        SecureField("", text: $userViewModel.passwordText)
        Text(userViewModel.passwordError).foregroundColor(.red)
            .frame(width: 264, alignment: .trailing)
    }
}

视图模型

ViewModel

final class UserViewModel: ObservableObject {

    private var cancellables = Set<AnyCancellable>()
    @Published var userText: String = ""
    @Published var userTextError = ""

    private var usernamevalidation: AnyPublisher<(username:String, isValid: Bool), Never> {
        return $userText
            .dropFirst()
            .map{(username:[=11=], isValid: ![=11=].isEmpty)}
            .eraseToAnyPublisher()
    }

    private var usernamevalidated: AnyPublisher<Bool,Never> {
        return usernamevalidation
            .filter{[=11=].isValid}
            .map{[=11=].username.isValidUserName()}
            .eraseToAnyPublisher()
    }


    init(){
        usernamevalidation.receive(on: RunLoop.main)
            .map{[=11=].isValid ? "": "Emptyusername "}
            .assign(to: \.userTextError, on: self)
            .store(in: &cancellables)
        usernamevalidated.receive(on: RunLoop.main)
            .map{[=11=] ? "" : "wrong username "}
            .assign(to: \.userTextError, on: self)
            .store(in: &cancellables)
    }
}

扩展

 extension String {

     func isValidUserName() -> Bool {
         let usernameRegex = "^[a-zA-Z0-9_-]*$"
         let usernamepred = NSPredicate(format:"SELF MATCHES %@", usernameRegex)
         return usernamepred.evaluate(with: self)
   }
}

在 ViewModel 的 init() 块中验证的用户名中,我将错误分配给 userTextError 属性,这应该反映在文本视图中。如果输入了特殊字符,如 @ 或 % .. 等,就会发生这种情况。发生的情况是,有时错误会以红色显示,而其他错误则不会显示,即使我尝试在 map 运算符之后打印字符串的值,我也可以看到打印正常的字符串。只是错误有时会反映在视图中,有时不会。我是漏掉了什么还是做错了什么

之前的答案(如果需要请查看编辑)是不正确的,因为 Emptyusername 错误将被覆盖,这不是我们需要的(我的错误)。

事实证明,问题是 iOS 14 更新 UI!我之前不得不为 TextField 使用的修复程序看起来有点不寻常,但确实有效。

只需在视图正文中添加:

var body: some View {
    let _ = userViewModel.userTextError

    /* Rest of view as before */
}

不要为您的发布商使用可计算的存储属性(以保持它们的生命力),例如

private lazy var usernamevalidation: AnyPublisher<(username:String, isValid: Bool), Never> = {
    return $userText
        .dropFirst()
        .map{(username:[=10=], isValid: ![=10=].isEmpty)}
        .eraseToAnyPublisher()
}()

问题是 usernamevalidationusernamevalidated 都是计算属性。存储它们将解决问题,但您也可以通过观察对 userText 的更改、验证它们并分配给 userTextError 来简化视图模型,如下所示:

final class UserViewModel: ObservableObject {
    
    @Published var userText: String = ""
    @Published private(set) var userTextError = ""
    
    init() {
        $userText
            .dropFirst()
            .map { username in
                guard !username.isEmpty else {
                    return "Username is empty"
                }
                guard username.isValidUserName() else {
                    return "Username is invalid"
                }
                return ""
            }
            .receive(on: RunLoop.main)
            .assign(to: &$userTextError)
    } 
}

还值得一提的是,将 .assign(to: \.userTextError, on: self) 替换为 .assign(to: &$userTextError) 可以消除内存泄漏,这意味着您确实需要将其存储在 cancellables 中。