Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
206 views
in Technique[技术] by (71.8m points)

authentication - SwiftUI determine initial view

I'm trying to figure out the navigation in the beginning of an app I'm making in the SwiftUI lifecycle. Depending on settings determined at launch, the user may start in one of three views. If the user opens the app from a push notification, they should see the video view (WrappedVideoViewController). Otherwise, if they are logged in already, they should see the Home view, or if not, the Login view. I'm using ContentView as the parent:

import SwiftUI

struct ContentView: View {
    @EnvironmentObject var notifService: NotificationService
    @EnvironmentObject var authState: AuthenticationState
    
    @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
        
    @ObservedObject var viewRouter: ViewRouter
    
    init() {
        self.viewRouter = ViewRouter.shared
    }
    
    var body: some View {
        
        print("in ContentView body")
        
        switch viewRouter.currentScreen {
        case .home:
            print("home")
            return AnyView(Home(innerGradientColor: Color.homeActiveInner, outerGradientColor: Color.homeActiveOuter))
        case .login:
            print("login")
            return
                AnyView(Login()
                .environmentObject(AuthenticationState.shared))
        case .video:
            print("video")
            return AnyView(WrappedVideoViewController())

        }
            
    }

}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
            .environmentObject(AuthenticationState.shared)
            .environmentObject(NotificationService())
    }
}

My ViewRouter class is an ObservableObject meant to control which screen is shown. Currently, I have this:

//
//  NavigationState.swift
//  BumpCall-SwiftUI
//
//  Created by Ben on 1/22/21.
//

import Foundation
import SwiftUI

enum Screen {
    case home
    case video
    case login
}

class ViewRouter: ObservableObject {
    
    var authState: AuthenticationState
    var notifService: NotificationService
    
    static var shared = ViewRouter(authState: AuthenticationState.shared, notifService: NotificationService.shared)
    
    init(authState: AuthenticationState, notifService: NotificationService) {
        self.authState = authState
        self.notifService = notifService
        
        if notifService.dumbData != nil {
            currentScreen = .video
        } else if (notifService.dumbData == nil && authState.isLoggedIn()) {
            currentScreen = .home
        } else {
            currentScreen = .login
        }
        
    }
    
    @Published var currentScreen: Screen
    
}

It's tricky because the ViewRouter is dependent on two other ObservableObjects, which I'm trying to handle here with the init(). It appears that the AuthenticationState class is working fine because upon opening the app, it does send you to the home screen if you are logged in. However, opening from a notification also sends me to the home screen. NotificationService changes the ViewRouter singleton in the didReceive response: method:

class NotificationService: NSObject, ObservableObject {
    @Published var dumbData: UNNotificationResponse?
    
    static var shared = NotificationService()
    
    override init() {
        super.init()
        UNUserNotificationCenter.current().delegate = self
    }
}

extension NotificationService: UNUserNotificationCenterDelegate  {
    func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
        print("presenting notification")
    }

    func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
        ViewRouter.shared.notifService.dumbData = response
        print("Notification service didReceive response")
        completionHandler()
    }

    func userNotificationCenter(_ center: UNUserNotificationCenter, openSettingsFor notification: UNNotification?) { }
    
}

But the singleton must not update for ContentView to notice. This is super convoluted and it's been confusing me for days, any help would be much appreciated.

question from:https://stackoverflow.com/questions/65863608/swiftui-determine-initial-view

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

The problem is that ViewRouter init() will be called once. As you mentioned, when the user is logged in, currentScreen will be initialized to .home. However this variable will never be changed afterwards.

What you basically want, is to set currentScreen to .video inside the Notification delegate.

func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
    ViewRouter.shared.currentScreen = .video //<< here set video 
    print("Notification service didReceive response")
    completionHandler()
}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...