一、简介
<<UIWindow类定义,管理和协调的Windows应用程序显示在屏幕上的对象(如Windows)。一个窗口的两个主要职能是,为显示其意见面积和分发活动的意见。窗口是在视图层次的根。一个窗口属于一个级别;一个级别的窗口出现另一个层面以上。例如,警报出现高于正常的窗口。通常情况下,只有一个在IOS应用程序的窗口
<<UIWindow :UIWindow是一种特殊的UIView,通常在一个app中只会有一个UIWindow
<<继承关系:UIWindow --> UIView -->UIResponder-->NSObject
<<它包含了应用中的可见内容;
<<它在视图和应用对象之间传递触摸事件中起很重要的作用;
<<它和视图控制器配合完成方向转变
在iOS系统中,windows没有标题栏、关闭框以及其他可视控件。一个window就是一个或多个视图的空白容器,而且应用不能通过window来改变自己的显示内容。当你想要改变现显示容的时候,改变最上层的视图就可以了。
大多数iOS应用在其生命周期内只使用一个window,这个window从应用的主nib文件中加载,铺满整个主屏幕。当然,如果你的应用支持外部显示,它会额外创建一个window,用于外部显示。系统会创建其他典型的window,一般是在响应特殊事件时创建,例如来电显示。
格式为
1--> 设置根视图(属性的作用)
[self.window.rootviewcontroller=vc];; (这是具体的例子)
@property(nullable, nonatomic,strong) UIViewController *rootViewController NS_AVAILABLE_IOS(4_0); // 设置根视图, 默认是空的 (这是属性的说明)
二、UIWindow的创建和配置
可以代码或者InterfaceBuilder来创建并设置window。您在启动时创建了窗口,并应保留它,并将其保存到应用程序代理对象中的引用。如果需要额外的window,则在需要用的时候创建即可。
创建window不需要考虑应用实在前台启动还是后台启动。创建和设置一个window不需要花太多资源。如果程序直接在后台启动,那么你就不能让window可见,直到window到前台之后再让其可见
1、InterfaceBuilder创建UIWindow
用IB创建window非常简单,因为Xcode的工程模板可以替你你创建。每个应用都会包含一个主XIB文件,这个XIB就包含了一个主window。另外,这些模板也为window在应用的代理对象中定义了outlet,你可以在代码中通过outlet取到window对象。
注意:在使用IB创建window的时候,应该在属性设置栏中设置为全屏。如果没有设置为全屏,且window小于手机的屏幕尺寸,那么一些视图的触摸时间肯接收不到。这是因为window接受不到自己区域以外的触摸事件。如果视图的触摸点没在window的区域范围内,则响应不到触摸事件。所以要确保window是全屏。
如果你重构代码时用到 IB添加window,也很简单,像XIB文件中拖一个window对象,然后进行如下操作:
要在运行时访问window,应该把window和outlet相连。outlet一般情况下是在application delegate中,也可以是这个NIB文件对应的代码文件。
重构过程中如果需要新建一个主NIB,那就得在info.plist文件中设置NSMainNibFil键。通过设置NSMainNibFil的值来确保这个window在代理方法application:didFinishLaunchingWithOptions:被调用是得到加载。
2、纯代码创建window
上面代码中的self.window,是在application delegate中已经声明过得属性,用来保存window对象。如果你创建的是用于外部显示的window,应该给它重新命名,并且需要设置bounds。
创建window的时候,要把bounds设置为屏幕大小,不能有任何缩减
三、将view添加到UIWindow
1、直接将控制器的view添加到UIWindow中,并不理会它对应的控制器
[self.window addsubview:vc.view];
直接将view通过addSubview方式添加到window中,程序负责维护view的生命周期以及刷新,但是并不会为去理会view对应的ViewController,因此采用这种方法将view添加到window以后,我们还要保持view对应的ViewController的有效性,不能过早释放。
2、设置uiwindow的根控制器,自动将rootviewcontroller的view添加到window中,负责管理rootviewcontroller的生命周期
[self.window.rootviewcontroller=vc];
rootViewController时UIWindow的一个遍历方法,通过设置该属性为要添加view对应的ViewController,UIWindow将会自动将其view添加到当前window中,同时负责ViewController和view的生命周期的维护,防止其过早释放
<注意>建议使用(2).因为方法(1)存在一些问题,比如说控制器上面可能由按钮,需要监听按钮的点击事件,如果是1,那么按钮的事件应该由控制器来进行管理。但控制器是一个局部变量,控制器此时已经不存在了,但是控制器的view还在,此时有可能会报错。注意:方法执行完,这个控制器就已经不存在了。
问题描述1:当view发生一些事件的时候,通知控制器,但是控制器已经销毁了,所以可能出现未知的错误。
问题描述2:添加一个开关按钮,让屏幕360度旋转(两者的效果不一样)。当发生屏幕旋转事件的时候,UIapplication对象会将旋转事件传递给uiwindow,uiwindow又会将旋转事件传递给它的根控制器,由根控制器决定是否需要旋转
UIapplication->uiwindow->根控制器(第一种方式没有根控制器,所以不能跟着旋转)。
提示:不通过控制器的view也可以做开发,但是在实际开发中,不要这么做,不要直接把view添加到UIWindow上面去。因为,难以管理。
四、获取window
1、主窗口和次窗口
【self.window makekeyandvisible】让窗口成为主窗口,并且显示出来。有这个方法,才能把信息显示到屏幕上。
因为Window有makekeyandvisible这个方法,可以让这个Window凭空的显示出来,而其他的view没有这个方法,所以它只能依赖于Window,Window显示出来后,view才依附在Window上显示出来。
【self.window make keywindow】//让uiwindow成为主窗口,但不显示。
2.获取UIwindow
1)[UIApplication sharedApplication].windows 在本应用中打开的UIWindow列表,这样就可以接触应用中的任何一个UIView对象(平时输入文字弹出的键盘,就处在一个新的UIWindow中)
(2)[UIApplication sharedApplication].keyWindow(获取应用程序的主窗口)用来接收键盘以及非触摸类的消息事件的UIWindow,而且程序中每个时刻只能有一个UIWindow是keyWindow。
提示:如果某个UIWindow内部的文本框不能输入文字,可能是因为这个UIWindow不是keyWindow
(3)view.window获得某个UIView所在的UIWindow
五、UIWindow的视图属性(属性的顺序与苹果API一致)
1-->设置Screen
self.window.screen = self.externalScreen;
@property(nonatomic,strong) UIScreen *screen NS_AVAILABLE_IOS(3_2);//默认是[UIScreen mainScreen]。改变屏幕可能是一个昂贵的操作,不应该在性能敏感的代码中完成
2-->设置视图层级
self.window.windowLevel = UIWindowLevelAlert+1;
@property(nonatomic) UIWindowLevel windowLevel; // 默认为0
<注意>UIWindow在显示的时候会根据UIWindowLevel进行排序的,即Level高的将排在所有Level比他低的层级的前面。下面我们来看UIWindowLevel的定义:
const UIWindowLevel UIWindowLevelNormal; 默认为0
const UIWindowLevel UIWindowLevelAlert;默认为2000
const UIWindowLevel UIWindowLevelStatusBar;默认为1000
typedef CGFloat UIWindowLevel;
Normal ,StatusBar,Alert.输出他们三个层级的值,我们发现从左到右依次是0,1000,2000,也就是说Normal级别是最低的,StatusBar处于中级,Alert级别最高。而通常我们的程序的界面都是处于Normal这个级别的,系统顶部的状态栏应该是处于StatusBar级别,提醒用户等操作位于Alert级别。根据window显示级别优先原则,级别高的会显示在最上层,级别低的在下面,我们程序正常显示的view在最底层;
3-->是否为根视图(只读属性)
BOOL isKeyWindow=self.window.keyWindow;
@property(nonatomic,readonly,getter=isKeyWindow) BOOL keyWindow;
4-->becomeKeyWindow
- (void)becomeKeyWindow; //该方法不应该被手动调用,当window变为keyWindow时会被自动调用来通知window。可以继承UIWindow重写此方法来实现功能
5-->resignKeyWindow
- (void)resignKeyWindow; // 该方法不应该被手动调用,当window不再是keyWindow时(例如其他window实例调用了- makeKeyWindow或- makeKeyAndVisible方法)会被自动调用来通知window。可以继承UIWindow重写此方法来实现功能。
6-->让当前UIWindow变成keyWindow,默认不显示
[self.window makeKeyWindow];
- (void)makeKeyWindow; // 让window成为keyWindow(主窗口),默认不可见
7-->让当前UIWindow变成keyWindow,并显示出来
[self.window makeKeyAndVisible];
- (void)makeKeyAndVisible;// 让当前UIWindow变成keyWindow,并显示出来
8-->设置uiwindow的根控制器
[self.window.rootviewcontroller=vc];
@property(nullable, nonatomic,strong) UIViewController *rootViewController NS_AVAILABLE_IOS(4_0);// 根控制器
9-->分发自定义事件
UIApplication 和 UIWindow 有方法 - sendEvent: ,用于把事件分发到 hitTest View
UIApplication == sendEvent: ==> UIWindow == sendEvent: ==> hitTest View
- (void)sendEvent:(UIEvent *)event;//UIApplication调用window的该方法给window分发事件,window再将事件分发到合适的目标,比如将触摸事件分发到真正触摸的view上。可以直接调用该方法分发自定义事件。
10-->把该window中的一个坐标转换成在目标window中时的坐标值
CGPoint p = [self.window1 convertPoint:CGPointMake(0, 0) toWindow:self.window0];
- (CGPoint)convertPoint:(CGPoint)point toWindow:(nullable UIWindow *)window;
11-->把目标window中的一个坐标转换成在该window中时的坐标值
CGPoint p = [self.window1 convertPoint:CGPointMake(0, 0) fromWindow:self.window0];
- (CGPoint)convertPoint:(CGPoint)point fromWindow:(UIWindow *)window;
12-->把该window中的一个矩阵转换成在目标window中时的矩阵值
CGRect rect=[self.window convertRect:CGRectMake(0, 0, 0, 0) toView:self.window0];
- (CGRect)convertRect:(CGRect)rect toWindow:(UIWindow *)window;
13--> 把目标window中的一个矩阵转换成在该window中时的矩阵值
CGRect rect=[self.window convertRect:CGRectMake(0, 0, 0, 0) toView:self.window0];
- (CGRect)convertRect:(CGRect)rect fromWindow:(UIWindow *)window;
六、UIWindow的常量属性
1-->UIWindowLevel的枚举
UIWindowLevelNormal;// 0.000000
UIWindowLevelStatusBar;// 1000.000000
UIWindowLevelAlert;// 2000.000000
2-->监测window的通知名称:
UIKIT_EXTERN NSString *const UIWindowDidBecomeVisibleNotification; // 当window激活时并展示在界面的时候触发,返回空
UIKIT_EXTERN NSString *const UIWindowDidBecomeHiddenNotification; // 当window隐藏的时候触发,暂时没有实际测,返回空
UIKIT_EXTERN NSString *const UIWindowDidBecomeKeyNotification; // 当window被设置为keyWindow时触发,返回空
UIKIT_EXTERN NSString *const UIWindowDidResignKeyNotification; // 当window的key位置被取代时触发,返回空
3-->监测键盘的通知名称:
UIKIT_EXTERN NSString *const UIKeyboardWillShowNotification;// 显示键盘的时候立即发出该通知
UIKIT_EXTERN NSString *const UIKeyboardDidShowNotification;//显示键盘后才发出该通知
UIKIT_EXTERN NSString *const UIKeyboardWillHideNotification;//键盘即将消失的时候立即发出该通知
UIKIT_EXTERN NSString *const UIKeyboardDidHideNotification;//键盘消失后才发出该通知
UIKIT_EXTERN NSString *const UIKeyboardWillChangeFrameNotification NS_AVAILABLE_IOS(5_0);//键盘的frame值发生变化的时候立即发出该通知
UIKIT_EXTERN NSString *const UIKeyboardDidChangeFrameNotification NS_AVAILABLE_IOS(5_0);//键盘的frame值发生变化后才发出该通知
4-->userInfo字典中key为:
NSString *const UIKeyboardFrameBeginUserInfoKey;//userInfo字典里该key对应一个NSValue,存储一个包含键盘初始frame值的CGRect结构(即键盘刚出现时的frame值)
NSString *const UIKeyboardFrameEndUserInfoKey;//userInfo字典里该key对应一个NSValue,存储一个包含键盘结束frame值的CGRect结构(即键盘动画结束后的frame值)
NSString *const UIKeyboardAnimationDurationUserInfoKey;//userInfo字典里该key对应一个NSNumber,存储一个包含键盘进入或离开屏幕的UIViewAnimationCurve结构
NSString *const UIKeyboardAnimationCurveUserInfoKey;//userInfo字典里该key对应一个NSNumber,存储一个包含键盘动画时间的double值,时间以秒为单位。
例如,在UIKeyboardWillShowNotification,UIKeyboardDidShowNotification通知中的userInfo内容为
userInfo = {
UIKeyboardAnimationCurveUserInfoKey = 0;
UIKeyboardAnimationDurationUserInfoKey = "0.25";
UIKeyboardBoundsUserInfoKey = "NSRect: {{0, 0}, {320, 216}}";
UIKeyboardCenterBeginUserInfoKey = "NSPoint: {160, 588}";
UIKeyboardCenterEndUserInfoKey = "NSPoint: {160, 372}";
UIKeyboardFrameBeginUserInfoKey = "NSRect: {{0, 480}, {320, 216}}";
UIKeyboardFrameChangedByUserInteraction = 0;
UIKeyboardFrameEndUserInfoKey = "NSRect: {{0, 264}, {320, 216}}";
}
参考: