无法将类型 'Published<Bool>.Publisher' 的值转换为预期的参数类型 'Binding<Bool>'

Cannot convert value of type 'Published<Bool>.Publisher' to expected argument type 'Binding<Bool>'

尝试编译以下代码时:

class LoginViewModel: ObservableObject, Identifiable {
    @Published var mailAdress: String = ""
    @Published var password: String = ""
    @Published var showRegister = false
    @Published var showPasswordReset = false

    private let applicationStore: ApplicationStore

    init(applicationStore: ApplicationStore) {
        self.applicationStore = applicationStore
    }

    var passwordResetView: some View {
        PasswordResetView(isPresented: $showPasswordReset) // This is where the error happens
    }
}

PasswordResetView 看起来像这样:

struct PasswordResetView: View {
    @Binding var isPresented: Bool
    @State var mailAddress: String = ""
    
    var body: some View {
            EmptyView()
        }
    }
}

我得到错误编译错误

Cannot convert value of type 'Published<Bool>.Publisher' to expected argument type 'Binding<Bool>'

如果我从 LoginViewModel 外部使用已发布的变量 class 它就可以正常工作:

struct LoginView: View {
    @ObservedObject var viewModel: LoginViewModel

    init(viewModel: LoginViewModel) {
      self.viewModel = viewModel
    }
    
    var body: some View {
            PasswordResetView(isPresented: self.$viewModel.showPasswordReset)
    }
}

对我做错了什么有什么建议吗?我是否有机会从拥有的 class?

中将已发布的变量作为绑定传递

谢谢!

** Combine 和 SwiftUI 还是新手,所以不确定是否有更好的方法**

您可以从发布者初始化绑定。

https://developer.apple.com/documentation/swiftui/binding/init(get:set:)-6g3d5

let binding = Binding(
    get: { [weak self] in
        (self?.showPasswordReset ?? false)
    },
    set: { [weak self] in
        self?.showPasswordReset = [=10=]
    }
)

PasswordResetView(isPresented: binding)

这是可能的方法 - 在生成的视图中进行观察并避免工厂和演示者之间的紧密耦合的想法。

使用 Xcode 12 / iOS 14 进行测试(对于较旧的系统,可能需要进行一些调整)

protocol ResetViewModel {
    var showPasswordReset: Bool { get set }
}

struct PasswordResetView<Model: ResetViewModel & ObservableObject>: View {
    @ObservedObject var resetModel: Model

    var body: some View {
        if resetModel.showPasswordReset {
            Text("Show password reset")
        } else {
            Text("Show something else")
        }
    }
}

class LoginViewModel: ObservableObject, Identifiable, ResetViewModel {
    @Published var mailAdress: String = ""
    @Published var password: String = ""
    @Published var showRegister = false
    @Published var showPasswordReset = false

    private let applicationStore: ApplicationStore

    init(applicationStore: ApplicationStore) {
        self.applicationStore = applicationStore
    }

    var passwordResetView: some View {
        PasswordResetView(resetModel: self)
    }
}

对于指出“无法将类型 'Binding' 的值转换为预期的参数类型 'Bool'”的错误,解决方案是使用 wrappedValue,如下例所示。

如果您的 MyObject 对象具有 属性 isEnabled 并且您需要将其用作 vanilla Bool 类型而不是 'Binding' 然后执行此操作 myView.disabled($myObject.isEnabled.wrappedValue)

我认为这里要理解的重要一点是“$”在 Combine 上下文中的作用。

“$”的作用是将变量“showPasswordReset”的变化发布到被观察到的地方。

当它在类型前面时,它不代表你为变量声明的类型(在这种情况下是布尔值),它代表一个发布者,如果你想要类型的值,只需去掉“$” .

“$”用于变量被标记为@ObservedObject 的上下文中, (这里的ObservableObject是LoginViewModel,你作为发布者订阅它来监听它变量市场的变化)

struct ContentView: View {
       @ObservedObject var loginViewModel: LoginViewModel...

在该上下文(例如 ContentView)中,“showPasswordReset”的值更新时的更改将是 'Published',因此视图会更新为新值。

不知道为什么这里提出的解决方案如此复杂,虽然有一个非常直接的解决方案。

similar Reddit question 上找到了这个答案:

The problem is that you are accessing the projected value of an @Published (which is a Publisher) when you should instead be accessing the projected value of an @ObservedObject (which is a Binding), that is: you have globalSettings.$tutorialView where you should have $globalSettings.tutorialView.