从现有的 SwiftUI @States 派生绑定

Posted

技术标签:

【中文标题】从现有的 SwiftUI @States 派生绑定【英文标题】:Deriving binding from existing SwiftUI @States 【发布时间】:2019-10-25 00:27:52 【问题描述】:

我一直在玩 SwiftUI 和 Combine,感觉可能有一种方法可以在视图中获取现有的 @State 属性并创建一个新属性。

例如,我有一个密码创建视图,其中包含用户的密码和密码确认字段。我想获取这两个@State 属性并派生一个新的@State,我可以在我的视图中使用它来断言输入是否有效。所以为简单起见:不为空且相等。

Apple 文档在绑定上说 there is a publisher,但我似乎无法掌握它。

这是一些不起作用的伪代码:

import SwiftUI
import Combine

struct CreatePasswordView : View 
    @State var password = ""
    @State var confirmation = ""
    lazy var valid = 
        return self.$password.publisher()
            .combineLatest(self.$confirmation)
            .map  $0 != "" && $0 == $1 
    

    var body: some View 
        SecureField($password, placeholder: Text("password"))

        SecureField($confirmation, placeholder: Text("confirm password"))

        NavigationButton(destination: NextView())  Text("Done") 
            .disabled(!valid)
    

任何人都找到了。解决这个问题的适当方式/如果可能的话?

更新 Beta 2:

从 beta 2 开始,发布者可用,因此该代码的前半部分现在可以使用。在 View 中使用生成的发布者的后半部分我还没有弄清楚 (disabled(!valid))。

import SwiftUI
import Combine

struct CreatePasswordView : View 
    @State var password = ""
    @State var confirmation = ""

    lazy var valid = 
        Publishers.CombineLatest(
            password.publisher(),
            confirmation.publisher(),
            transform:  String($0) != "" && $0 == $1 
        )
    ()

    var body: some View 
        SecureField($password, placeholder: Text("password"))

        SecureField($confirmation, placeholder: Text("confirm password"))

        NavigationButton(destination: NextView())  Text("Done") 
            .disabled(!valid)
    

谢谢。

【问题讨论】:

你试过var valid: Bool password != "" && password == confirmation 吗?因为passwordconfirmation 都是@State,所以body 在它们发生变化时总是会重新计算,valid 将反映新的状态 @RicoCrescenzio 我认为这是正确的想法 【参考方案1】:

你需要的是一个计算属性。顾名思义,每次访问属性时都会重新计算其值。如果你使用@State变量来计算属性,SwiftUI会在valid发生变化时自动重新计算View的body:

struct CreatePasswordView: View 
    @State var password = ""
    @State var confirmation = ""

    private var valid: Bool 
        password != "" && password == confirmation
    

    var body: some View 
        SecureField($password, placeholder: Text("password"))

        SecureField($confirmation, placeholder: Text("confirm password"))

        NavigationLink(destination: NextView())  Text("Done") 
            .disabled(!valid)
    

【讨论】:

【参考方案2】:

我不会玩 @State/@Published,因为 Combine 目前处于测试阶段,但这里有一个简单的解决方法,可以解决您想要实现的目标。

我会实现一个视图模型来保存密码、密码确认以及它是否有效

class ViewModel: NSObject, BindableObject 

    var didChange = PassthroughSubject<Void, Never>()

    var password: String = "" 
        didSet 
            didChange.send(())
        
    

    var passwordConfirmation: String = "" 
        didSet 
            didChange.send(())
        
    

    var isPasswordValid: Bool 
        return password == passwordConfirmation && password != ""
    


这样,只要密码或确认发生变化,就会重新计算视图。

然后我会为视图模型创建一个@ObjectBinding

struct CreatePasswordView : View 

    @ObjectBinding var viewModel: ViewModel

    var body: some View 
        NavigationView 
            VStack 
                SecureField($viewModel.password,
                            placeholder: Text("password"))
                SecureField($viewModel.passwordConfirmation,
                            placeholder: Text("confirm password"))
                NavigationButton(destination: EmptyView())  Text("Done") 
                    .disabled(!viewModel.isPasswordValid)
            
        
    


我不得不将视图放在NavigationView 中,因为如果NavigationButton 不在其中之一中,它似乎不会启用自己。

【讨论】:

你为什么说Combine被窃听了?您在使用 Combine 时遇到过什么重大问题? @SMP 我的意思是beta - Swift 编译器还没有完全准备好像@State/@Published 这样的@propertyWrapper/@propertyDelegate attributes。最后,Foundation 集成无法按照 Xcode 11 beta release notes 工作 - 搜索 51241500。 欣赏这个,谢谢。我实际上已经尝试过这种方法,尽管我实际上想做的不仅仅是简单的验证,还想发出一个带去抖动的网络请求(检查haveibeenpwned.com API 是否有泄露的密码)。 Apple 有 WWDC 视频,介绍了类似的集成 UIKit+Combine 的东西 - 也许 Combine+SwiftUI 还没有,或者他们有其他的想法。 @freebie 怀疑它主要是幻灯片,而不是实时编码 - 他们不想冒红点的风险!

以上是关于从现有的 SwiftUI @States 派生绑定的主要内容,如果未能解决你的问题,请参考以下文章

从现有的 docker 容器中删除端口绑定

如何从现有的远程分支创建本地分支?

从现有的 JAVA Spring API 生成 YAML 文件

从现有的 Xcode 项目制作 CocoaPod

tcpdf - 从现有的 PDF 文档开始

c#从现有的对象数组创建一个新列表[关闭]