Search code examples
iosswiftswiftuicombine

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


When trying to compile the following code:

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
    }
}

Where PasswordResetView looks like this:

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

I get the error compile error

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

If I use the published variable from outside the LoginViewModel class it just works fine:

struct LoginView: View {
    @ObservedObject var viewModel: LoginViewModel

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

Any suggestions what I am doing wrong here? Any chance I can pass a published variable as a binding from inside the owning class?

Thanks!


Solution

  • Here is possible approach - the idea to make possible observation in generated view and avoid tight coupling between factory & presenter.

    Tested with Xcode 12 / iOS 14 (for older systems some tuning might be needed)

    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)
        }
    }