現在有一個需求:
在判斷出用戶token過期,或者沒有登錄的時候顯示登錄界面
這個需求實現起來有三個方法:
1 改變UIApplication的window的根控制器
2 在當前控制器present出來登錄界面
3 通過添加window顯示登錄界面
前兩種方法的弊端:
第一種方法會銷毀之前的控制器,登陸成功之后需要重新加載,可能無法回到登陸之前的界面
第二種方法只能在控制器中實現,如果是在一個view中,就無法用這種方式實現
綜上所述,第三種方法是最通用,效率最高的
關於UIWindow的介紹:
一、UIWindow特點
(1)UIWindow 是一種特殊的 UIView,通常在一個 app 中至少會有三個 UIWindow:
- app delegate里的window
- 狀態欄的window(比較特殊,雖然在程序內部可以調用某些api顯示隱藏或改變其UI,但它的window是不被我們的應用程序內部所持有的)
- 鍵盤的window
(2)iOS程序啟動完畢后,創建的第一個視圖控件就是 UIWindow,接着創建控制器的 view,最后將控制器的 view 添加到 UIWindow 上,於是控制器的 view 就顯示在屏幕上了;
(3)一個iOS程序之所以能顯示到屏幕上,完全是因為它有 UIWindow,也就說,沒有UIWindow,就看不見任何UI界面。
二、添加 UIView 到 UIWindow 中兩種常見方式:
(1)- (void)addSubview:(UIView )view;*
直接將 view 添加到 UIWindow 中,但並不會理會 view 對應的 UIViewController;
(2)設置window的rootViewController;
這種做法會自動將 rootViewController 的 view 添加到 UIWindow 中,是蘋果推薦的做法
三、UIWindow 常用方法
- (void)makeKeyWindow;
讓當前 UIWindow 變成 keyWindow(主窗口);
- (void)makeKeyAndVisible;
讓當前 UIWindow 變成 keyWindow,並顯示出來。
四、UIWindow 的獲得
[UIApplication sharedApplication].windows
在本應用中打開的 UIWindow 列表,這樣就可以接觸應用中的任何一個 UIView 對象;
(平時輸入文字彈出的鍵盤,就處在一個新的 UIWindow 中)
[UIApplication sharedApplication].keyWindow
用來接收鍵盤以及非觸摸類的消息事件的 UIWindow,而且程序中每個時刻只能有一個 UIWindow 是 keyWindow。如果某個 UIWindow 內部的文本框不能輸入文字,可能是因為這個 UIWindow 不是 keyWindow。
如果想通過這個方法改變keyWindow的rootviewcontroller來改變顯示的界面,可能無法成功
window,多個時,[UIApplication sharedApplication].windows[0]和[[UIApplication sharedApplication] delegate] window]獲取的是最開始創建的window,而[UIApplication sharedApplication].keyWindow獲取的是當前顯示的window,不一定是最初的window
正確的代碼是
UIWindow * win = [UIApplication sharedApplication].delegate.window; [win setRootViewController:vc]; [win makeKeyAndVisible];
五、UIWindow 層級
self.window.windowLevel = UIWindowLevelAlert;
UIWindow 有三個層級:UIWindowLevelAlert, UIWindowLevelStatusBar, UIWindowLevelNormal,
Normal,StatusBar,Alert 分別 為 0,1000,2000
windowLevel 是 CGFloat 類型,可以進行加減運算,或自定義優先級
現在我們來解決問題:創建一個自己的window類,根控制器設置為登陸控制器,通過控制window的hidden屬性來顯示或隱藏,然后在需要的時候將window顯示出來,登陸完成后隱藏
注意:想要自己的window顯示,就要保證windwo對象不能被銷毀,需要保持在內存中,比較方便的做法是將window設置成單例,就可以保存在內存中,在不需要的時候設置為nil,釋放內存
#import <UIKit/UIKit.h> @interface LogInWindow : UIWindow + (instancetype)sharedLogInWindow; + (void)hideWindow; @end #import "LogInWindow.h" #import "LogInVC.h" static LogInWindow * __logWin; @implementation LogInWindow + (instancetype)sharedLogInWindow{ static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ __logWin = [[LogInWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; UIStoryboard * logSB = [UIStoryboard storyboardWithName:@"LogInSB" bundle:[NSBundle mainBundle]]; UINavigationController * logNav = [logSB instantiateViewControllerWithIdentifier:@"LogInNav"]; __logWin.rootViewController = logNav; }); return __logWin; } + (void)hideWindow{ [LogInWindow sharedLogInWindow].hidden = YES; } @end
UIWindow使用需要注意的地方:
切換根控制器可能會造成內存泄漏:如果present了一個控制器,在當前控制器切換rootViewController,而沒有執行dismiss操作,就會導致控制器無法釋放,造成內存泄漏
在切換控制器之前需要執行dismiss操作
[self dismissViewControllerAnimated:YES completion:^{ //在這里更換根控制器 }];
獲取最上層window的方法:
- (UIWindow *)lastWindow { NSArray *windows = [UIApplication sharedApplication].windows; for(UIWindow *window in [windows reverseObjectEnumerator]) { if ([window isKindOfClass:[UIWindow class]] && CGRectEqualToRect(window.bounds, [UIScreen mainScreen].bounds)) return window; } return [UIApplication sharedApplication].keyWindow; }
https://www.jianshu.com/p/98cd7fc4bfba這個是大神寫的,超級厲害
https://yq.aliyun.com/articles/62456 這個阿里雲的文章寫的雲里霧里,但是好像很牛逼的樣子,先存起來