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

14,264

Solution 1

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

Solution 2

** Still new to Combine & SwiftUI so not sure if there is better way to approach **

You can initalize Binding from publisher.

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 = $0
    }
)

PasswordResetView(isPresented: binding)

Solution 3

I think the important thing to understand here is what "$" does in the Combine context.

What "$" does is to publish the changes of the variable "showPasswordReset" where it is being observed.

when it precedes a type, it doesn't represent the type you declared for the variable (Boolean in this case), it represents a publisher, if you want the value of the type, just remove the "$".

"$" is used in the context where a variable was marked as an @ObservedObject, (the ObservableObject here is LoginViewModel and you subscribe to it to listen for changes in its variables market as publishers)

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

in that context (the ContentView for example) the changes of "showPasswordReset" are going to be 'Published' when its value is updated so the view is updated with the new value.

Share:
14,264
Jan Koch
Author by

Jan Koch

Updated on June 06, 2022

Comments

  • Jan Koch
    Jan Koch almost 2 years

    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!

  • Jan Koch
    Jan Koch almost 4 years
    Thanks. I saw that as well but it looks a bit clunky. But I will try it out now :-)
  • B Porr
    B Porr about 3 years
    That's a very smart idea by just putting the ObservedObject in the view and with that preventing any type issues. I use it to update a progress bar which is monitoring a published progress value in another thread and works perfectly.
  • Ahmadreza
    Ahmadreza about 3 years
    I get Cannot find 'self' in scope
  • obevan
    obevan about 2 years
    This should be the accepted answer!
  • Jayson
    Jayson about 2 years
    That makes so much sense I don't know why I didn't think about it. lol
  • Hajji Daoud
    Hajji Daoud about 2 years
    Can't believe this actually worked
  • Parth Mehrotra
    Parth Mehrotra about 2 years
    Most people probably have globalSettings.$tutorialView because that's what XCode suggests while you're typing.