2024-04-29|閱讀時間 ‧ 約 29 分鐘

App啟動時,利用firebase remoteConfig來決定初始畫面

最近碰到個神奇的需求,App啟動時,才要用firebase remoteConfig去決定初始畫面

Q: 但一開始沒畫面要怎麼辦?_?

A: 做一個假啟動畫面給他

一般的啟動流程

App啟動 ➜ Launch Screen ➜ didFinishLaunching開始 ➜ 設定rootViewController ➜ didFinishLaunching結束

Launch Screen是 App 啟動時,向使用者展示的第一個畫面。顯示的時間長短是系統控制的,開發者無法控制。也沒辦法在上面打API做一些有的沒的。

所以我們要做的是在didFinishLaunching先去設一個跟launch Screen一模一樣的畫面,到這個假啟動畫面上去拿remoteConfig,讓使用者感覺我們還在啟動中。等到去firebase上拿完value,再跳轉過去。

  1. AppDelegate裡先設定假的啟動畫面當rootViewController
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{
// ...
// 設定RootViewController
    FakeSplashViewController * splashVC = [[FakeSplashViewController alloc] init];

    [self.window setRootViewController: splashVC];
// ...

[self.window makeKeyAndVisible];

    return YES;

}
  1. 再到假啟動畫面的viewDidAppear裡,去firebase上拿值

注意:不要寫在viewDidLoad裡,有可能啟動還沒完成就去換root導致crash!!

class FakeSplashViewController: UIViewController { 

   override func viewDidLoad() {
        super.viewDidLoad()
    }

    override func viewDidAppear(_ animated: Bool) {

        super.viewDidAppear(animated)

        // 延遲換rootViewController,確保整個啟動流程已執行完畢

        self.setRootViewController()

    }

}
  1. 置換rootViewController
func setRootViewController() {
let version = RemoteConfigHelper.shared.fetchStringValue(key: RemoteConfigKey.version.rawValue)
    if version == "1" {
        let tabVC = V1TabBarController()
        appDelegate.window.rootViewController = tabVC
        Router.shared.setRootController(rootController: tabVC)
} else {
        let tabVC = V2TabBarController()
        appDelegate.window.rootViewController = tabVC
        Router.shared.setRootController(rootController: tabVC)
    }

}

推播的情況

上面的方案感覺完美,但是有一種情境是在App未啟動的情況下,點擊推播開啟指定頁面,這時候因為App還未設定好你要的rootViewController,造成跳轉無效。

例如:rootViewController應該要是UINavigationController,點擊推播用push跳轉頁面。但是在未完成拿到firebase的情況下,root還是假的啟動頁面,造成跳轉無效。

所以我們要確定rootViewController已設成我們要的,再做跳轉行為。

1.AppDelegate收到通知

#pragma mark - UNUserNotificationCenterDelegate

//在背景收到通知

- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void(^)(void))completionHandler {
    NSDictionary *userInfo = response.notification.request.content.userInfo;
    [[PushNotificationManager shared] receiveWithUserInfo: userInfo];
}
  1. 在PushNotificationManager設定推播跳轉頁面,如果isRootComplete還沒完成,就先把Action存起來。
@objcMembers class PushNotificationManager: NSObject {
    static let shared = PushNotificationManager()
    var pendingAction: ActionData?
    struct ActionData {
        var dict: [String: String]?
    }
    private override init() {}
    func receive(userInfo: [String: Any]?) {
        Task { @MainActor in
            await receiveRemoteNotification(userInfo: userInfo)
        }
    }
    private func receiveRemoteNotification(userInfo: [String: Any]?) async {
       // …拿一些必要參數
       if LoginManager.shared.isRootComplete {
// root已經完成,直接跳轉
let vc = MyViewController(dict: dict)
            Router.shared.push(vc)
       } else {
// root設置還沒完成,先存起來
    pendingAction = ActionData(dict: dict)
        }
    }

    @MainActor
    func processPendingTransferAction() {
      guard let action = pendingAction else {
           return
      }
      receiveRemoteNotification(userInfo: pendingAction.dict)
// 做完記得清掉​
      pendingAction = nil
    }
}
  1. 回到假啟動畫面FakeSplashViewController,如果已從firebase拿完,把isCompleteRoot設成true,並執行還未進行的Action。
class FakeSplashViewController: UIViewController { 
// ...
func setRootViewController() {
// ...拿完firebase remoteConfig
   LoginManager.shared.isCompleteRoot = true
    PushNotificationManager.shared.processPendingTransferAction()
}
}






分享至
成為作者繼續創作的動力吧!
紀錄iOS開發上遇到的問題或是一些流程筆記。主要都是Swift。
© 2024 vocus All rights reserved.