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?
I had accidently had 2 nested NavigationViews
s 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.