本文并非最终版本,如果想要关注更新或更正的内容请关注文集,联系方式详见文末,如有疏忽和遗漏,欢迎指正。
本文相关目录:
【Xcode 7.3】UIWindow.h - UIKit
1.0 UIWindow ->1.0 NSWindow - 窗口
1.0 UIWindow ->2.0 UIWindow简介和创建过程
1.0 UIWindow的简介
UIWindow是一种特殊的UIView,通常在一个app中只会有一个UIWindow
一个iOS程序之所以能显示到屏幕上,完全是因为它有UIWindow , 也就说,没有UIWindow,就看不见任何UI界面
2.0 UIWindow的创建过程
【简版】UIWindow的创建过程:
iOS程序启动完毕后
①首先创建的第一个视图控件就是UIWindow
②接着创建控制器的view
③最后将控制器的view添加到UIWindow上,于是控制器的view就显示在屏幕上了
以上过程如下图:
【详版】有storyboard的情况下,UIWindow的创建过程:(通过SB加载程序,窗口都交给SB管理,不需要自己管理。)
- 先执行Main函数,执行UIApplicationMain()
- 根据其第三个和第四个参数创建Application,创建代理,并且把代理设置给application
- 根据项目配置文件info.plist里面的storyboard的name,找到对应的storyboard
- 接下来创建一个window,之后创建它的初始化控制器(就是箭头所指向的控制器)
- 自动把该控制器设置为UIWindow的根控制器
- 接下来再将window显示出来,即看到了运行后显示的界面。
【详版】没有storyboard的情况下,UIWindow的创建过程:(代码创建窗口是程序启动完毕的时候才创建的)
- 先执行Main函数,执行UIApplicationMain()
- 根据其第三个和第四个参数创建Application,创建代理,并且把代理设置给application
- 开启一个事件循环,当程序加载完毕,他会调用代理的didFinishLaunchingWithOptions:方法
- 在该方法中,会创建一个Window,然后创建一个控制器
- 并把该控制器设置为UIWindow的根控制器
- 接下来再将window显示出来,即看到了运行后显示的界面。
2.1 UIWindow的获取
相关方法如下:
// 在本应用中打开的UIWindow列表 (这样就可以接触应用中的任何一个UIView对象)
// (平时输入文字弹出的键盘,就处在一个新的UIWindow中)
[UIApplication sharedApplication].windows
// 获取应用程序的主窗口
// keyWindow是指用来接收键盘以及非触摸类的消息事件的UIWindow,且程序中每个时刻只能有一个UIWindow是keyWindow)
// 如果某个UIWindow内部的文本框不能输入文字,可能是因为这个UIWindow不是keyWindow
[UIApplication sharedApplication].keyWindow
// 获得某个UIView所在的UIWindow
view.window
.
2.2 添加UIView到UIWindow中
添加UIView到UIWindow中两种常见方式:
1、addsubview:直接将view添加到UIWindow中,但并不会理会view对应的UIViewController
- (void)addSubview:(UIView *)view;
举例:
[self.window addSubview:vc.view];
2、rootViewController:自动将rootViewController的view添加到UIWindow中,负责管理rootViewController的生命周期(推荐)
// NS_AVAILABLE_IOS(4_0) default is nil
@property(nullable, nonatomic,strong) UIViewController *rootViewController ;
举例:
self.window.rootViewController = vc;
两种添加控制器view的方式区别:
问题1:可能不能处理点击事件(未知的bug)
- 当view被销毁,方法1无法处理点击事件等操作,会造成一些未知错误(野指针)
- 控制器上面可能由按钮,需要监听按钮的点击事件,如果是1,那么按钮的事件应该由控制器来进行管理。但控制器是一个局部变量,控制器此时已经不存在了,但是控制器的view还在,此时有可能会报错。注意:方法执行完,这个控制器就已经不存在了。
问题2:旋转事件
- 添加一个开关按钮,让屏幕360度旋转(两者的效果不一样)。
- 当发生屏幕旋转事件的时候,UIapplication对象会将旋转事件传递给UIWindow,UIWindow又会将旋转事件传递给它的根控制器,由根控制器决定是否需要旋转。
- UIapplication->UIWindow->根控制器(第一种方式没有根控制器,所以不能跟着旋转)。
提示:不通过控制器的view也可以做开发,但是在实际开发中,不要这么做,不要直接把view添加到UIWindow上面去。因为,难以管理。
总结:addSubView和rootViewController的区别
- 直接用addSubView,控制器会被释放,控制器就不能处理事件
- 直接用addSubView,控制器的view不会自动旋转。
- 用rootViewController,控制器不会被释放,而且控制器的view会自动旋转
- 旋转事件-‐> UIApplication -‐> Window -‐> rootViewController -‐> 旋转控制器的view
两种方法的使用场景举例:
- 以后可能会有很多界面,为了使得代码结构清晰,通过一个界面交给一个控制器
rootViewControllerd的常见用处:切换控制器。比如新特性界面展示完成,切换到主界面的时候,
改窗口的根控制器就好了,新特性界面就会自动销毁,它只需要展示一次。
2.3 UIWindow的显示
UIWindow的显示常用方法:
// 让当前UIWindow(窗口)变成keyWindow(主窗口),但不显示
- (void)makeKeyWindow;
举例:
// 让UIwindow成为主窗口
[self.window makekeywindow];
// 让当前UIWindow(窗口)变成keyWindow(主窗口),并显示出来
- (void)makeKeyAndVisible;
举例:
// 让窗口成为主窗口
[self.window makekeyandvisible];
说明:
- 只有方法2,可以让Window凭空的显示出来,才能把信息显示到屏幕上
- 其他的view没有这个方法,所以只能依赖于Window,Window显示出来后,view才依附在Window
上显示出来。
2.4 UIWindow的主窗口和次窗口
疑问:
一个应用程序只能有一个主窗口,如果程序中创建了两个Window,那么谁是主窗口?
答案:
①iOS 7 以后,主窗口和次窗口是没有区别的
②iOS 7 之前,如果后面的窗口设置为主窗口,会把之前设置的主窗口覆盖掉
证明(一个案例):
代码示例:
AppDelegate.h
#import
// UIWindow的属性定义为strong,是为了让其不销毁
@interface AppDelegate : UIResponder
@property(strong, nonatomic) UIWindow *window1; // 窗口1
@property(strong, nonatomic) UIWindow *window2; // 窗口2
@end
AppDelegate.m
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
#pragma mark - UIwindow1(红色)
// 1.创建UIwindow1
self.window1 = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
self.window1.backgroundColor = [UIColor redColor];
// 创建一个控制器
UIViewController *vc = [[UIViewController alloc] init];
// 2.把控制器的view添加到UIwindow1上
// 方法1: addsubview (此法本案例会报错)
[self.window1 addSubview:vc.view];
// 方法2:rootViewController
self.window1.rootViewController = vc;
// 3.显示出来UIwindow1,
// 方法1:让UIwindow1成为keyWindow(主窗口),并且可见
[self.window1 makeKeyAndVisible];
// 方法2:让UIwindow1成为keyWindow(主窗口),默认不可见
[self.window1 makeKeyWindow];
// 给UIwindow1添加一个输入框
UITextField *tf = [[UITextField alloc] init];
tf.frame = CGRectMake(10, 64, 100, 20);
tf.borderStyle = UITextBorderStyleRoundedRect;
[self.window1 addSubview:tf];
// 打印当前应用的主窗口,此时应用的主窗口是window
NSLog(@"%p", [UIApplication sharedApplication].keyWindow);
#pragma mark - UIwindow2
// 1.创建UIwindow2
self.window2 =
[[UIWindow alloc] initWithFrame:CGRectMake(100, 150, 200, 200)];
self.window2.backgroundColor = [UIColor blueColor];
// 2.把控制器的view添加到UIwindow2上
// 方法1: addsubview (此法本案例会报错)
[self.window2 addSubview:vc.view];
// 方法2:rootViewController
self.window2.rootViewController = vc;
// 3.显示出来UIwindow2
// 方法1:让UIwindow2成为keyWindow(主窗口),并且可见
[self.window2 makeKeyAndVisible];
// 方法2:让UIwindow2成为keyWindow(主窗口),默认不可见
[self.window2 makeKeyWindow];
// 给UIwindow2添加一个输入框
UITextField *tf2 = [[UITextField alloc] init];
tf2.frame = CGRectMake(30, 30, 70, 20);
tf2.borderStyle = UITextBorderStyleRoundedRect;
[self.window2 addSubview:tf2];
// 打印当前应用的主窗口,此时window2已经成为了应用的主窗口
NSLog(@"%p", [UIApplication sharedApplication].keyWindow);
return YES;
}
运行效果:
结论:只有主窗口才能响应键盘的输入事件
- 从代码中我们可以得知,红色部分的window不是主窗口,蓝色部分的window是主窗口。
- 在ios9.3的模拟器中,主窗口和非主窗口中的输入框都能输入文字,但是在ios6.1的模拟器中,
非主窗口的输入框不能输入文字。(运行效果图是ios9.3的模拟器)
3.0 UIWindow的层级问题
UIWindowLevel:
有三个层级,分别是Normal,StatusBar,Alert
UIKIT_EXTERN const UIWindowLevel UIWindowLevelNormal;
UIKIT_EXTERN const UIWindowLevel UIWindowLevelAlert;
UIKIT_EXTERN const UIWindowLevel UIWindowLevelStatusBar;
层级说明:
- 这三个层级的值从左到右依次是0,1000,2000
- 也就是说Normal级别是最低的,StatusBar处于中等水平,Alert级别最高。
层级应用场景:
- 通常程序的界面都是处于Normal这个级别上的
- 系统顶部的状态栏应该是处于StatusBar级别
- UIActionSheet和UIAlertView这些通常都是用来中断正常流程,提醒用户等操作,因此位于Alert级别。
总结:
objc把UIWindow的层级设置为UIWindowLevelAlert ,就会显示在最前面。
本文源码 Demo 详见 Github
https://github.com/shorfng/iOS-2.2-UIKit
作者:蓝田(Loto)
出处:
如果你觉得本篇文章对你有所帮助,请点击文章末尾下方“喜欢”
如有疑问,请通过以下方式交流:
① 评论区回复
② 微信(加好友请注明“+称呼”)
③发送邮件
至 [email protected]
本文版权归作者和本网站共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。。