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 = ""
...
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.