现在有一个需求:
在判断出用户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 这个阿里云的文章写的云里雾里,但是好像很牛逼的样子,先存起来