Home > Enterprise >  Button not running the action
Button not running the action

Time:01-15

I am building a login for my app which calls an API on my server and returns user information and a JWT token. I decided to split my views into separate code blocks with a view to moving them to their own files in the project later on. Since I have done this however, my button in the login form doesn't run the login function in my ViewModel, nor does it even print and output to the console.

I have included my ContentView.swift here to show my thinking and basic setup.

struct ContentView: View {
    
    @StateObject private var loginVM = LoginViewModel()
    @StateObject private var projectListVM = ProjectListViewModel()
    @State private var isSecured: Bool = true
    
    var body: some View {
        Group {
            if loginVM.isAuthenticated {
                MainView()
            } else {
                LoginView()
            }
        }
    }
}


struct LoginView: View {
    @StateObject private var loginVM = LoginViewModel()
    @StateObject private var projectListVM = ProjectListViewModel()
    @State private var isPasswordVisible = false
    var body: some View {
        Form {
            VStack(alignment: .center, spacing: 0.0) {
                Spacer()
                VStack(alignment: .leading, spacing: 10)  {
                    Text("Email")
                        .font(.fieldLabel)
                        .foregroundColor(Color.secondary01Light)
                        .frame(alignment: .leading)
                    TextField("Username", text: $loginVM.username)
                        .keyboardType(.emailAddress)
                        .textInputAutocapitalization(.never)
                        .padding()
                        .overlay {
                            RoundedRectangle(cornerRadius: 4, style: .continuous)
                                .stroke(Color(UIColor.systemGray4), lineWidth: 1)
                        }
                }
                
                VStack(alignment: .leading, spacing: 10) {
                    Spacer()
                    Text("Password")
                        .font(.fieldLabel)
                        .foregroundColor(Color.secondary01Light)
                        .frame(alignment: .leading)
                    SecureField("Password", text: $loginVM.password)
                        .padding()
                        .overlay {
                            RoundedRectangle(cornerRadius: 4, style: .continuous)
                                .stroke(Color(UIColor.systemGray4), lineWidth: 1)
                        }
                }
            }
            
            VStack {
                Button(action: {
                    print("Login Tapped")
                    loginVM.login()
                }) {
                    Text("Sign In")
                        .frame(maxWidth: .infinity)
                        .padding(8)
                        .cornerRadius(8)
                }
                .padding(.top, 30)
                .frame(height: nil)
                .buttonStyle(.borderedProminent)
                .tint(Color.primary01)
                .controlSize(.regular)
                .font(.custom("Poppins-ExtraBold", size: 16))
                .foregroundColor(.white)
            }           
        }
    }
}

The above code shows the button inside a form and with an action and inside of the action a call to the login() method and a print statement.

When I tap the button in the simulator I am not even getting an output in the console and it doesn't seem to be running my login function either.

Below is my LoginViewModel which I have had working without a problem in the past.

class LoginViewModel: ObservableObject {
    
    var username: String = ""
    var password: String = ""
    @Published var isAuthenticated: Bool = false
    
    func login() {
        print("Login Called")
        Webservice().login(username: username, password: password) { result in
            print("Login API Call Made")
            switch result {
                case .success(_):
                    print("Got User Data")
                    DispatchQueue.main.async {
                        self.isAuthenticated = true
                    }
                case .failure(let error):
                    print(error.localizedDescription)
            }
        }
        
    }
}

I have the Form fields in place and the @StateObject in the view but doesn't seem to want to fire.

CodePudding user response:

You initialize loginVM two times! Once in ContentView and again in LoginView (both using @StateObject) ... so you create two different states that are not connected.

What you want to do is create it only once in ContentView and pass it down (e.g. with .environmentObject) to LoginView:

struct ContentView: View {
    
    // here you create/initialize the object
    @StateObject private var loginVM = LoginViewModel()
    
    var body: some View {
        Group {
            if loginVM.isAuthenticated {
                MainView()
            } else {
                LoginView()
            }
        }
        .environmentObject(loginVM) // pass the object to all child views
    }
}


struct LoginView: View {

    // here you get the passed Object from the environment
    @EnvironmentObject private var loginVM: LoginViewModel

    var body: some View {
        // ...
    }
}

  • Related