iOS -- UIWindow的使用

现在有一个需求:

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

 

posted @ 2018-08-28 16:16  电蚊拍  阅读(2919)  评论(0编辑  收藏  举报