为啥即使发布者发出一个值,我的视图也没有在 SwiftUI 中呈现

Posted

技术标签:

【中文标题】为啥即使发布者发出一个值,我的视图也没有在 SwiftUI 中呈现【英文标题】:Why my views are not Rendered in SwiftUI even though the Publisher emits a value为什么即使发布者发出一个值,我的视图也没有在 SwiftUI 中呈现 【发布时间】:2021-06-30 00:39:20 【问题描述】:

我在 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:$0, isValid: !$0.isEmpty)
            .eraseToAnyPublisher()
    

    private var usernamevalidated: AnyPublisher<Bool,Never> 
        return usernamevalidation
            .filter$0.isValid
            .map$0.username.isValidUserName()
            .eraseToAnyPublisher()
    


    init()
        usernamevalidation.receive(on: RunLoop.main)
            .map$0.isValid ? "": "Emptyusername "
            .assign(to: \.userTextError, on: self)
            .store(in: &cancellables)
        usernamevalidated.receive(on: RunLoop.main)
            .map$0 ? "" : "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() 块中的 usernamevalidated 中,我将错误分配给 userTextError 属性,该属性应该反映在 textview 中。如果输入了 @ 或 % .. 等特殊字符,就会发生这种情况。发生的情况是,有时即使我尝试在映射运算符之后打印字符串的值,错误也会以红色显示,而其他错误则显示为红色,我可以看到打印正常的字符串。只是错误有时会反映在视图中,有时不会。我是否遗漏了什么或做一些根本错误的事情

【问题讨论】:

【参考方案1】:

问题在于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: &amp;$userTextError) 可以消除内存泄漏,这意味着您确实需要将其存储在cancellables 中。

【讨论】:

这很好用,但是为什么将发布者设为私有时它不起作用 UserText 发布者不能是私有的,因为视图需要读取和写入它。 UserTextError 可以是私有的(设置),因为视图需要能够读取它,但不需要写入它。【参考方案2】:

而不是为您的发布者使用可计算的存储属性(以使它们保持活力),例如

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

【讨论】:

仍然无法正常工作,特别是 usernamevalidated 发布者,有时会显示错误,有时不会 我在每个发布者的 dropfirst 之后发现了我的问题,我需要在主线程上接收这些。添加以下 .receive(on: DispatchQueue.main) 解决了我的问题【参考方案3】:

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

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

只需在视图正文中添加:

var body: some View 
    let _ = userViewModel.userTextError

    /* Rest of view as before */

【讨论】:

尽管您说的有道理,但问题仍然存在。问题是有时会显示错误,有时不会。我不知道这是 xcode 中的错误还是什么,但它很痛苦 @Ahmed 在此更改之后,它对我有用。使用 Xcode 13b2 'Version 13.0 beta (13A5155e)' 测试 让我检查一下我的 xcode 版本。您可以尝试多次,因为它有时有效,有时无效。喜欢尝试6-10次,你能和我确认一下吗 @Ahmed 啊哈!我的适用于 iOS 15 上的模拟器和真实设备,但不适用于 iOS 14 上的模拟器。 @Ahmed 老实说,我不确定为什么会有所不同,但我会让你去调查。好像是iOS版本的问题,目前我只知道这些

以上是关于为啥即使发布者发出一个值,我的视图也没有在 SwiftUI 中呈现的主要内容,如果未能解决你的问题,请参考以下文章

为啥我的子视图没有显示在堆栈顶部?

为啥我的 onBindViewHolder () 根本没有运行,即使我的所有其他回收器视图方法都运行了?

为啥即使我使用 Alamofire 发出请求,我的应用程序仍然冻结?

即使没有变化也能发出流量

为啥我的 WASAPI 监听器即使在没有播放的时候也会触发?

为啥我的状态值即使在改变状态后也没有改变? [复制]