最近碰到個神奇的需求,App啟動時,才要用firebase remoteConfig去決定初始畫面
Q: 但一開始沒畫面要怎麼辦?_?
A: 做一個假啟動畫面給他
App啟動 ➜ Launch Screen ➜ didFinishLaunching開始 ➜ 設定rootViewController ➜ didFinishLaunching結束
Launch Screen是 App 啟動時,向使用者展示的第一個畫面。顯示的時間長短是系統控制的,開發者無法控制。也沒辦法在上面打API做一些有的沒的。
所以我們要做的是在didFinishLaunching先去設一個跟launch Screen一模一樣的畫面,到這個假啟動畫面上去拿remoteConfig,讓使用者感覺我們還在啟動中。等到去firebase上拿完value,再跳轉過去。
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{
// ...
// 設定RootViewController
FakeSplashViewController * splashVC = [[FakeSplashViewController alloc] init];
[self.window setRootViewController: splashVC];
// ...
[self.window makeKeyAndVisible];
return YES;
}
注意:不要寫在viewDidLoad裡,有可能啟動還沒完成就去換root導致crash!!
class FakeSplashViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// 延遲換rootViewController,確保整個啟動流程已執行完畢
self.setRootViewController()
}
}
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];
}
@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
}
}
class FakeSplashViewController: UIViewController {
// ...
func setRootViewController() {
// ...拿完firebase remoteConfig
LoginManager.shared.isCompleteRoot = true
PushNotificationManager.shared.processPendingTransferAction()
}
}