Search code examples
swiftfirebaseswiftuiuikitgoogle-signin

Sign in with google swiftui not working as expected with navigation


I would like to allow users to sign in with google in my Swift/SwiftUI app. I've followed the examples given by Google and included my code below. https://developers.google.com/identity/sign-in/ios/sign-in https://firebase.google.com/docs/auth/ios/google-signin

When the sign In with google process starts, the user is navigated back to the Home/Root view of the app. Losing all progress in the sign up flow should they cancel the sign up. (See gif)

My question is, is there a way to allow the user to sign in with google from the 3rd page in my sign in flow without having them returned to the root view if they canceled google sign in? It seems every example online has the 'Sign up' button on the root view and so this really isn't a problem in those cases since there is no progress to be lost in a sign up flow.

10s example of the error happening

Home Screen

struct LoginOrSignupView: View {
    @State var loginShown = false
    @State var signUpShown = false
    
    var body: some View {
        NavigationView {
            VStack {
                VStack {
                    Spacer()
                    
                    Text("My App")
                        .font(.custom("landing-screen-title", size: 60, relativeTo: .title))
                        .padding()
                    
                    Spacer()
                }
                .font(.custom("landing-screen", size: 25, relativeTo: .title))
                .foregroundColor(.white)
                
                VStack(spacing: 15) {
                    Spacer()
                    
                    NavigationLink {
                        AccountCreationDetails()
                    } label: {
                        Text("Sign Up")
                            .frame(width: 200)
                            .padding()
                            .background(.white)
                            .cornerRadius(5)
                            .foregroundColor(.black)
                            .font(.subheadline)
                    }
                    
                    Spacer()
                }
            }
            .dynamicTypeSize(...DynamicTypeSize.accessibility2)
            .background(
                Image("CafeBookshelf")
                    .resizable()
                    .aspectRatio(contentMode: .fill)
                    .blur(radius: 15, opaque: true)
            )
            .ignoresSafeArea()
        }
    }
}

Second Screen

struct AccountCreationDetails: View {
    @StateObject var signUpViewModel = SignUpViewModel()
    
    var body: some View {
        ZStack {
            Color("Light2")
                .edgesIgnoringSafeArea(.all)
            
            VStack(alignment: .leading, spacing: 10) {
                Text("Just 2 quick questions...")
                    .font(.custom("sips-font", size: 32, relativeTo: .title))
                    .padding(.bottom, 30)
                
                VStack(spacing: 20) {
                    NavigationLink {
                        ChooseAccountCreationMethodView(signUpViewModel: signUpViewModel)
                    } label: {
                        Text("Continue")
                            .padding()
                            .background(.blue)
                            .cornerRadius(5)
                            .foregroundColor(Color("Light1"))
                    }
                }
            }
            .padding()
        }
    }
}

Last Screen

struct ChooseAccountCreationMethodView: View {
    @EnvironmentObject var userManager: UserManager
    @ObservedObject var signUpViewModel: SignUpViewModel
    
    var body: some View {
        ZStack {
            Color("Light2")
                .edgesIgnoringSafeArea(.all)
            
            VStack {
                
                VStack {
                    Button {
                        Task {
                            await userManager.signInWithGoogle()
                        }
                    } label: {
                        HStack {
                            Image("GoogleLogo")
                                .resizable()
                                .aspectRatio(contentMode: .fit)
                                .frame(height: 30)
                            
                            Text("Continue with Google")
                                .bold()
                                .foregroundColor(.primary)
                                .frame(maxWidth: .infinity)
                        }
                        .padding(5)
                    }
                    .buttonStyle(.bordered)
                }
                .padding(40)
            }
        }
    }
}
lass UserManager : ObservableObject {
    @Published var currentUser: FirebaseAuth.User?
    
    init() {
        Auth.auth().addStateDidChangeListener { auth, user in
            self.currentUser = user
        }
    }

    enum AuthenticationError: Error {
        case tokenError(message: String)
    }
    

    @MainActor
    func signInWithGoogle() async -> Bool {
        guard let clientID = FirebaseApp.app()?.options.clientID else {
            fatalError("No client ID found in Firebase configuration")
        }
        
        let config = GIDConfiguration(clientID: clientID)
        GIDSignIn.sharedInstance.configuration = config
        
        guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
              let window = windowScene.windows.first,
              let rootViewController = window.rootViewController else {
            print("There is no root view controller!")
            return false
        }
        
        do {
            let userAuthentication = try await GIDSignIn.sharedInstance.signIn(withPresenting: rootViewController)
            
            let user = userAuthentication.user
            guard let idToken = user.idToken else { throw AuthenticationError.tokenError(message: "ID token missing") }
            let accessToken = user.accessToken
            
            let credential = GoogleAuthProvider.credential(withIDToken: idToken.tokenString,
                                                           accessToken: accessToken.tokenString)
            
            let result = try await Auth.auth().signIn(with: credential)
            let firebaseUser = result.user
            print("User \(firebaseUser.uid) signed in with email \(firebaseUser.email ?? "unknown")")
            return true
        }
        catch {
            print(error.localizedDescription)
            return false
        }
    }
}

I've tried finding ways of retrieving the viewcontroller associated with my swiftUI view but have not had any luck finding something that works. Is there another approach to Google Sign in that integrates well with firebase that I could use?


Solution

  • I had accidently had 2 nested NavigationViewss and removing one of them fixed the issue. I found that presenting and dismissing a generic .sheet would trigger the same 'navigate back to root view' behavior.

    So in summary the issue was not related to google sign-in.