Nadia Siddiqah
Nadia Siddiqah

Reputation: 153

How to handle opening a URL to load a specific app screen?

I want to implement a functionality where when a user clicks a URL to reset their password, it directs them to the "PasswordReset" screen in my app.

Currently, when I click the URL (ex: https://app.myApp.com/auth/reset?token=1234), it opens up to the "FirstView" screen instead of the "PasswordReset" screen.

I noticed when I print the URL in the "myApp struct", it prints the correct URL above, but if I print the URL in the "RootView struct", it shows up as nil. What steps can I take to ensure that the URL loads to the "PasswordReset" screen?

@main
struct myApp: App {
    @State var route: URL?

    var body: some Scene {
        WindowGroup {
            RootView(url: route)
                .onOpenURL { url in
                    let _ = print(url)  // prints: https://app.myApp.com/auth/reset?token=1234
                    self.route = url
                }
        }
    }
}

struct RootView: View {
    @ObservedObject var authenticator = Authenticator.shared
    @State var url: URL?

    var body: some View {
        let _ = print(url) // prints: nil 

        // if user clicks on url to reset password, PasswordResetView should load
        if url?.pathComponents.contains("reset") ?? false,
                  let resetToken = url?.queryDictionary?["token"] {
            NavigationView {
                PasswordResetView(viewModel: .init(onFinish: { url = nil }))
                    .onAppear {
                        self.authenticator.accessToken = resetToken
                    }
            }

        // if user had accessToken (can log in), the MainTabView should load
        } else if authenticator.accessToken != nil {
            MainTabView()

        // if user has no accessToken, FirstView should load
        } else {
            NavigationView {
               FirstView()
            }
        }
    }
}

extension URL {
    var queryDictionary: [String: String]? {
        guard let query = self.query else { return nil }

        var queryStrings = [String: String]()
        for pair in query.components(separatedBy: "&") {
            let key = pair.components(separatedBy: "=")[0]

            let value = pair
                .components(separatedBy: "=")[1]
                .replacingOccurrences(of: "+", with: " ")
                .removingPercentEncoding ?? ""

            queryStrings[key] = value
        }
        return queryStrings
    }
}

Upvotes: 2

Views: 1557

Answers (1)

Nadia Siddiqah
Nadia Siddiqah

Reputation: 153

Here's my solution to this issue:

struct myApp: App {
    @ObservedObject var authenticator = Authenticator.shared
    @State var route: URL?
    @State var urlToken: String?
    
    var body: some Scene {
        WindowGroup {
            RootView(url: route)
                .onOpenURL { url in
                    self.route = url
                    if url.pathComponents.contains("reset"), let token = url.queryDictionary?["token"] {
                        self.urlToken = token
                    }
                }
                .fullScreenCover(item: $urlToken) { _ in
                    NavigationView {
                        PasswordResetView(viewModel: .init(onFinish: {
                            route = nil
                            urlToken = nil
                        }))
                        .onAppear {
                            self.authenticator.accessToken = urlToken
                        }
                    }
                }
        }
    }
}

struct RootView: View {
    @ObservedObject var authenticator = Authenticator.shared

    var body: some View {
        if authenticator.accessToken != nil {
            MainTabView()
        } else {
            NavigationView {
               FirstView()
            }
        }
    }
}

Upvotes: 2

Related Questions