Simulator Loading Issue

@ellieN1235

Unfortunately there are missing files. You need to compress the project at the very top level folder as shown in the following screenshot.

The zip file needs to include the City Sights App.xcodeproj file and the folder containing all the project files named City Sights App

Hi Chris,

Let me know if that doesnā€™t work
Thank you

@ellieN1235

Hi Ellie,

Yes, thatā€™s good. I have opened the project so now I have to create a project on Firebase link it to. Iā€™ll get back to you when I have got it working.

There is no GoogleService-Info.plist file included so I assume that you removed your copy before you compressed it?

@ellieN1235

Hi Ellie,

Iā€™ve got the project to build and run on a simulator and the first screen presented is the Login Sign Up View.

which looks OK to me.

There is no code in the actionButton as yet to either log the user in or create a new user (depending on which mode the View is in) so that is something for you to figure out. For the moment I have added a Binding to isLoggedIn from the LaunchView and tapping that button sets isLoggedIn to true which dismisses the LoginSignupView and takes the user to the OnBoardingView.

OK so what did I do?.

  • I added a GoogleService-Info.plist file
  • Fixed the error in LaunchView which was causing the error in this screenshot you posted

The problem was related to this in LaunchView:

LoginSignupView(onLoginSuccess: { isLoggedIn in
    self.isLoggedIn = isLoggedIn
})

This part of the above code:

onLoginSuccess: { isLoggedIn in
    self.isLoggedIn = isLoggedIn
}

was causing the error you were seeing. Xcode sometimes is very bad at pointing to the actual error. I removed that code since it did not make any sense and didnā€™t line up with anything in LoginSignupView and that got rid of the error.

The only changes I have made to the project are in LaunchView and LoginSignupView so if you modify your copies so that they match these you should see your project working.

LaunchView:

struct LaunchView: View {
    @EnvironmentObject var model: ContentModel
    @State private var isLoggedIn = false
    
    var body: some View {
        Group {
            if isLoggedIn {
                if model.authorizationState == .notDetermined {
                    OnboardingView()
                } else if model.authorizationState == .authorizedAlways || model.authorizationState == .authorizedWhenInUse {
                    HomeView()
                } else {
                    LocationDeniedView()
                }
            } else {
                LoginSignupView(isLoggedIn: $isLoggedIn)
            }
        }
    }
}

struct LaunchView_Previews: PreviewProvider {
    static var previews: some View {
        LaunchView()
            .environmentObject(ContentModel())
    }
}

LoginSignupView:

struct LoginSignupView: View {
    @ObservedObject var viewModel = LoginSignupViewModel()
    @Binding var isLoggedIn: Bool

    var emailTextField: some View {
        TextField("Email", text: $viewModel.emailText)
            .textFieldStyle(.roundedBorder)
            .autocorrectionDisabled(true)
            .autocapitalization(.none)
    }
    var passwordTextField: some View {
        SecureField("Password", text: $viewModel.passwordText)
            .textFieldStyle(.roundedBorder)
            .autocorrectionDisabled(true)
            .autocapitalization(.none)
    }

    var actionButton: some View {
        Button(viewModel.buttonTitle) {
            //action
            isLoggedIn = true
        }.padding()
            .frame(maxWidth: .infinity)
            .foregroundColor(.white)
            .background(Color(.systemPink))
            .cornerRadius(16)
            .padding()
    }
    var body: some View {
        VStack {
            Text(viewModel.title)
                .font(.largeTitle)
                .fontWeight(.bold)
            Text(viewModel.subtitle)
                .font(.title2)
                .fontWeight(.semibold)
                .foregroundColor(Color(.systemGray2))
            Spacer().frame(height: 50)
            emailTextField
            passwordTextField
            actionButton

            Button {
                if viewModel.mode == .login {
                    viewModel.mode = .signup
                } else {
                    viewModel.mode = .login
                }
            } label: {
                Text("Switch to \(viewModel.mode == .signup ? "Login" : "Sign-Up")")
            }

            Spacer()
        }
        .padding(.horizontal)
    }
}

struct LoginSignupView_Previews: PreviewProvider {
    static var viewModel = LoginSignupViewModel()
    static var previews: some View {

        NavigationView {
            LoginSignupView(isLoggedIn: .constant(false))
        }
        .environment(\.colorScheme, .dark)
        .environmentObject(viewModel)
    }
}

Cheers

1 Like

Hi Chris,

Thanks for pointing that out for me. I fixed it and it worked now!

I just added some codes for LoginSignupView and ContentModel to log users in and create new users. Iā€™m not receiving any errors this time, but itā€™s still not working as expected. I tried to sign up/login with my email but Iā€™m not seeing itā€™s going to the Onboarding View after and user data also not appearing on the Firebase page. Although it says I created a new user on Xcode console.

If you have some time to take a look this is the link to the project: City Sights App.zip - Google Drive

Hi Ellie,

I donā€™t see any code that is interacting with the Authentication section of Firebase to either log in an existing user or create a new user.

Example code to sign a user in (existing user) could be something like this:

Auth.auth().signIn(withEmail: email, password: password) { result, error in
    // Check for errors
    guard error == nil else {
        print(error!.localizedDescription)
        return
    }
    // Example of other code here as a result of a successful login
    isLoggedIn = true
}

Example code to create a new user could be something like this:

Auth.auth().createUser(withEmail: email, password: password) { result, error in

    guard error == nil else {
        print(error!.localizedDescription)
        return
    }

    // If you were collecting the name of the new user as part of the
    // log in credentials then what you could so is save the name 
    // to Firebase using the users UID as the document ID.
    let firebaseUser = Auth.auth().currentUser
    let db = Firestore.firestore()
    let ref = db.collection("users").document(firebaseUser!.uid)

    ref.setData(["name": name], merge: true)

    // Example of other code here as a result of successfully creating a new user
    isLoggedIn = true
}

These code segments require the swift file, in which they might be used, to import the following:
import Firebase
import FirebaseAuth

You would most likely do this in the LoginSignupView by adding the respective code to the performLogin() and createUser() functions.

There is a Button coded into the HomeView below the VStack inside the if !isMapShowing { statement. The Button will never be visible since it is outside of the VStack. That said, itā€™s not clear what you had intended to do with that Button anyway since the LogIn or SignUp is occuring before the HomeView is made visible.

The other thing that comes to mind is there needs to be provision for the user to log out.

Hi Chris,

I implemented the respective codes into LoginSignupView under performLogin() and createUser() functions. I received this ā€œCannot find ā€˜nameā€™ in scopeā€ error. Therefore I tried to define the name with " let name = viewModel.name" and received this error again -ā€œReferencing subscript ā€˜subscript(dynamicMember:)ā€™ requires wrapper ā€˜ObservedObject.Wrapperā€™ā€

Iā€™m not sure what I should do next. Do you have any advice?

My project: City Sights App.zip - Google Drive

Thank you

@ellieN1235

Hi Ellie,

I think I have confused you. In your createUser function, replace the code with the following. In other words ignore the references to name since your SignUp code does not capture the persons name.

    func createUser() {
        // Perform create user logic here
        // Use the `viewModel.emailText` and `viewModel.passwordText` to create a new user
        
        let email = viewModel.emailText
        let password = viewModel.passwordText
        
        Auth.auth().createUser(withEmail: email, password: password) { result, error in
            guard error == nil else {
                print(error!.localizedDescription)
                return
            }

            // Example of other code here as a result of successfully creating a new user
            isLoggedIn = true
        }
    }

Hi Chris,

That works! Now I can load the userā€™s data on Firebase whenever I try to log in or sign up for a new email. However, I can go to OnboardingScreen as long as I put an email and a random password in so I donā€™t feel like Iā€™m correctly implying the logic of authentication.

Can you give me guidelines to implement that logic?

Also, I have a question regarding SwiftUI vs. UIKit but I thought I would ask it here in 1 response (do let me know if I should put it out into a separate thread since it is off-topic). I started learning IOS back in February with the course Learn IOS in 90 Days and so Iā€™m most comfortable in SwiftUI. Now I realized that most apps out there are probably written in UIKit and so I thought if I do want to land my first job in IOS I might want to go back and learn UIKit. What are your thoughts on this? Should I continue with SwiftUI or go back and learn UIKit?

Vi

At the moment there is no validation of user inputs when creating a new user to make sure that they are supplying a valid email address that has the correct format.

The other thing is that there is no validation of the password to ensure that it follows some kind of standard such as at least 8 characters and it must have at least 1 capital letter and at least 1 lowercase letter and at least 1 other non alphabetic and non numeric character.

There is tons of validation code available online that can do both of those tasks to ensure that there is consistency.

When logging is as an existing user the same validation can be applied before the email and password is used in the Auth.auth().signIn(.....) process. At the moment, If an error occurs when logging in, the console will display what the error is because there is a print statement which gives feedback.

            guard error == nil else {
                print(error!.localizedDescription)
                return
            }

Thatā€™s no good for a user since he/she canā€™t see that.

What you need is feedback in the LoginSignUpView by use of an additional Text() element to show an error message to the user (which you populate) so they know what is going on in the background after they tap SignIn or Sign up.

Yes, create a separate thread.