skip to Main Content

I am developing a SwiftUI-based registration flow in my app, where a user progresses through several steps to complete their registration. The issue I’ve encountered involves passing an instance of UserData, which is an ObservableObject, to various child views as a Binding. Specifically, I’m stuck with the error message: "Cannot convert value of type ‘ObservedObject.Wrapper’ to expected argument type ‘Binding’" in my RegistrationView.

I have a RegistrationView that controls the registration process and several child views for each step of the registration (like EmailRegistrationStepView, PhoneNumberStepView, etc.). These child views need to update the shared UserData object.

Despite following the standard SwiftUI approach of using @ObservedObject and passing the data as a Binding using the $ syntax, I keep getting a type conversion error. This error occurs when I try to pass userData from the RegistrationView to its child views.

Attempted Solutions & Difficulties:

I’ve double-checked that userData is correctly declared as an @ObservedObject and that I’m using the $ prefix for creating a Binding.
I’ve cleaned the build folder and restarted Xcode.
I’m using Xcode Version 15.0.1.
I attempted to simplify the code to isolate the issue, but the error persists.

Code:

import SwiftUI

struct RegistrationView: View {
    enum RegistrationStep {
        case emailAndPassword
        case phoneNumber
        case verificationCode
        ...
    }

    @State private var registrationStep: RegistrationStep = .emailAndPassword
    @ObservedObject var userData: UserData

    var body: some View {
        VStack {
            switch registrationStep {
            case .emailAndPassword:
                EmailRegistrationStepView(nextStep: $registrationStep, userData: $userData)
            case .phoneNumber:
                PhoneNumberStepView(nextStep: $registrationStep, userData: $userData)
            case .verificationCode:
    ...

And then the user data is established in another file as a data model:

class UserData: ObservableObject {
    // Use @Published to automatically update views when these properties change.
    @Published var email: String = ""
    ...

2

Answers


  1. Chosen as BEST ANSWER

    I wanted to provide an update on my issue regarding the "Missing argument for parameter 'userData' in call" and "Cannot convert value of type 'ObservedObject.Wrapper' to expected argument type 'Binding'" errors I was encountering in my SwiftUI app.

    Thanks to the community's input, especially from workingdog support Ukraine, I explored the approach of not passing the entire ObservableObject (UserData) as a binding to my views. Instead, I passed its @Published properties as bindings. This insight was crucial in pointing me in the right direction.

    Here's a brief overview of the final solution:

    RegistrationView: I modified RegistrationView to pass only the specific @Published properties from UserData as bindings to the child step views. For instance, in the case of EmailRegistrationStepView, I passed email and password as bindings.

    Step Views: Each step view, such as EmailRegistrationStepView, PhoneNumberStepView, etc., was updated to accept only the necessary properties as @Binding parameters instead of expecting the entire UserData object.

    Local State in Step Views: For views like VerificationCodeStepView, where we initially thought of passing a verificationCode binding, I instead used a local @State variable to manage the verification code.

    This approach eliminated the type conversion errors and aligned well with SwiftUI's data flow principles. The app now correctly observes changes to UserData properties and updates the UI accordingly.

    To the Stack Overflow community, thank you for your guidance. The suggestion to pass individual @Published properties as bindings was particularly helpful. If there's any further advice or best practices I should consider, I'm all ears.

    @fromgisselle


  2. If you need to pass the whole userData model to your EmailRegistrationStepView and PhoneNumberStepView,
    then try this simple approach, using .environmentObject(userData) as shown in the example code:

    class UserData: ObservableObject {
        @Published var email: String = ""
        @Published var phoneNumber: String = ""
        // ...
    }
    
    // outside RegistrationView
    enum RegistrationStep {
        case emailAndPassword
        case phoneNumber
        // ...
    }
    
    struct RegistrationView: View {
        @State private var registrationStep: RegistrationStep = .emailAndPassword
        @EnvironmentObject var userData: UserData  // <-- here
        
        var body: some View {
            VStack {
                Text(userData.email).foregroundStyle(.red) // <-- to show it works
                
                switch registrationStep {
                case .emailAndPassword:
                    EmailRegistrationStepView(nextStep: $registrationStep) // <-- here
                case .phoneNumber:
                    PhoneNumberStepView(nextStep: $registrationStep)
                // ....
                }
            }
        }
    }
    
    struct EmailRegistrationStepView: View {
        @EnvironmentObject var userData: UserData  // <-- here
        @Binding var nextStep: RegistrationStep  // <-- here, only if you need to change nextStep
      
        var body: some View {
            Text("in EmailRegistrationStepView")
            TextField("email", text: $userData.email).border(.green)  // <-- here, change the email
        }
    }
    
    struct PhoneNumberStepView: View {
        @EnvironmentObject var userData: UserData
        @Binding var nextStep: RegistrationStep
    
        var body: some View {
            Text("PhoneNumberStepView")
            TextField("phoneNumber", text: $userData.phoneNumber)
        }
    }
    
    struct ContentView: View {
        @StateObject var userData = UserData()
        
        var body: some View {
            RegistrationView()
               .environmentObject(userData)  // <-- here
        }
    }
    

    Note, if you put enum RegistrationStep inside the RegistrationView,
    then you need to use @Binding var nextStep: RegistrationView.RegistrationStep etc…
    whenever you refer to it.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search