转载于 http://www.cnblogs.com/smileEvday/archive/2012/11/16/UIWindow.html
每一个IOS程序都有一个UIWindow,在我们通过模板简历工程的时候,xcode会自动帮我们生成一个window,然后让它变成keyWindow并显示出来。这一切都来的那么自然,以至于我们大部分时候都忽略了自己也是可以创建UIWindow对象。
通常在我们需要自定义UIAlertView的时候(IOS 5.0以前AlertView的背景样式等都不能换)我们可以使用UIWindow来实现(设置windowLevel为Alert级别),网上有很多例子,这里就不详细说了。
我的上一篇文章UIWindowLevel详解,中讲到了关于Windowlevel的东西,当时还只是看了看文档,知道有这么一回儿事。今天刚好遇到这块儿的问题,就顺便仔细看了一下UIWindow方面的东西,主要包括:WindowLevel以及keyWindow两个方面。
一、UIWindowLevel
我们都知道UIWindow有三个层级,分别是Normal,StatusBar,Alert。打印输出他们三个这三个层级的值我们发现从左到右依次是0,1000,2000,也就是说Normal级别是最低的,StatusBar处于中等水平,Alert级别最高。而通常我们的程序的界面都是处于Normal这个级别上的,系统顶部的状态栏应该是处于StatusBar级别,UIActionSheet和UIAlertView这些通常都是用来中断正常流程,提醒用户等操作,因此位于Alert级别。
上一篇文章中我也提到了一个猜想,既然三个级别的值之间相差1000,而且我们细心的话查看UIWindow的头文件就会发现有一个实例变量_windowSublevel,那我们就可以定义很多中间级别的Window。例如可以自定义比系统UIAlertView级别低一点儿的window。于是写了一个小demo,通过打印发现系统的UIAlertView的级别是1996,而与此同时UIActionSheet的级别是2001,这样也验证了subLevel的确存在。
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"Alert View" message:@"Hello Wolrd, i‘m AlertView!!!" delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:@"Cancel", nil]; [alertView show]; UIActionSheet *actionSheet = [[UIActionSheet alloc] initWithTitle:@"ActionSheet" delegate:nil cancelButtonTitle:@"Cancel" destructiveButtonTitle:@"Don‘t do that!" otherButtonTitles:@"Hello Wolrd", nil]; [actionSheet showInView:self.view];
下面是程序运行截图:
根据window显示级别优先的原则,级别高的会显示在上面,级别低的在下面,我们程序正常显示的view位于最底层,至于具体怎样获取UIAlertView和UIActionSheet的level,我会在下面第二部分keyWindow中介绍并给出相应的代码。
二、KeyWindow
什么是keyWindow,官方文档中是这样解释的"The key window is the one that is designated to receive keyboard and other non-touch related events. Only one window at a time may be the key window." 翻译过来就是说,keyWindow是指定的用来接收键盘以及非触摸类的消息,而且程序中每一个时刻只能有一个window是keyWindow。
下面我们写个简单的例子看看非keyWindow能不能接受键盘消息和触摸消息,程序中我们在view中添加一个UITextField,然后新建一个alert级别的window,然后通过makeKeyAndVisible让它变成keyWindow并显示出来。代码如下:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease]; // Override point for customization after application launch. self.viewController = [[[SvUIWindowViewController alloc] initWithNibName:@"SvUIWindowViewController" bundle:nil] autorelease]; self.window.rootViewController = self.viewController; [self.window makeKeyAndVisible]; UIWindow *window1 = [[UIWindow alloc] initWithFrame:CGRectMake(0, 80, 320, 320)]; window1.backgroundColor = [UIColor redColor]; window1.windowLevel = UIWindowLevelAlert; [window1 makeKeyAndVisible]; return YES; }
- (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. [self registerObserver]; // add a textfield UITextField *filed = [[UITextField alloc] initWithFrame:CGRectMake(0, 0, 320, 60)]; filed.placeholder = @"Input something here"; filed.clearsOnBeginEditing = YES; filed.borderStyle = UITextBorderStyleRoundedRect; [self.view addSubview:filed]; [filed release]; }
运行截图如下:
从图中可以看出,虽然我们自己新建了一个然后设置为keyWindow并显示,但是点击程序中默认window上添加的textField还是可以唤出键盘,而且还可以正常接受键盘输入,只是键盘被挡住了,说明非keyWindow也是可以接受键盘消息,这一点和文档上说的不太一样。(文档也没说必须是KeyWindow才能接受键盘消息)
观察UIWindow的文档,我们可以发现里面有四个关于window变化的通知:
UIWindowDidBecomeVisibleNotification
UIWindowDidBecomeHiddenNotification
UIWindowDidBecomeKeyNotification
UIWindowDidResignKeyNotification
这四个通知对象中的object都代表当前已显示(隐藏),已变成keyWindow(非keyWindow)的window对象,其中的userInfo则是空的。于是我们可以注册这个四个消息,再打印信息来观察keyWindow的变化以及window的显示,隐藏的变动。
代码如下:
// // SvUIWindowViewController.m // SvUIWindowSample // // Created by maple on 11/14/12. // Copyright (c) 2012 maple. All rights reserved. // #import "SvUIWindowViewController.h" #import "SvCustomWindow.h" @interface SvUIWindowViewController () - (void)presentAlertView; - (void)presentActionSheet; // observer - (void)registerObserver; - (void)unregisterObserver; // observer handler - (void)windowBecomeKey:(NSNotification*)noti; - (void)windowResignKey:(NSNotification*)noti; - (void)windowBecomeVisible:(NSNotification*)noti; - (void)windowBecomeHidden:(NSNotification*)noti; @end @implementation SvUIWindowViewController - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. [self registerObserver]; // add a textfield UITextField *filed = [[UITextField alloc] initWithFrame:CGRectMake(0, 0, 320, 60)]; filed.placeholder = @"Input something here"; filed.clearsOnBeginEditing = YES; filed.borderStyle = UITextBorderStyleRoundedRect; [self.view addSubview:filed]; [filed release]; } - (void)viewDidUnload { [super viewDidUnload]; // Release any retained subviews of the main view. [self unregisterObserver]; } - (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; // [self presentAlertView]; // [self presentActionSheet]; } - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown); } #pragma mark - #pragma mark observer - (void)registerObserver { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(windowBecomeKey:) name:UIWindowDidBecomeKeyNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(windowResignKey:) name:UIWindowDidResignKeyNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(windowBecomeVisible:) name:UIWindowDidBecomeVisibleNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(windowBecomeHidden:) name:UIWindowDidBecomeHiddenNotification object:nil]; } - (void)unregisterObserver { [[NSNotificationCenter defaultCenter] removeObserver:self]; } #pragma mark - #pragma mark Test AlertView & ActionSheet - (void)presentAlertView { UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"Alert View" message:@"Hello Wolrd, i‘m AlertView!!!" delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:@"Cancel", nil]; [alertView show]; [alertView release]; } - (void)presentActionSheet { UIActionSheet *actionSheet = [[UIActionSheet alloc] initWithTitle:@"ActionSheet" delegate:nil cancelButtonTitle:@"Cancel" destructiveButtonTitle:@"Don‘t do that!" otherButtonTitles:@"Hello Wolrd", nil]; [actionSheet showInView:self.view]; [actionSheet release]; } #pragma mark - #pragma mark Notification handler - (void)windowBecomeKey:(NSNotification*)noti { UIWindow *window = noti.object; NSArray *windows = [UIApplication sharedApplication].windows; NSLog(@"current window count %d", windows.count); NSLog(@"Window has become keyWindow: %@, window level: %f, index of windows: %d", window, window.windowLevel, [windows indexOfObject:window]); } - (void)windowResignKey:(NSNotification*)noti { UIWindow *window = noti.object; NSArray *windows = [UIApplication sharedApplication].windows; NSLog(@"current window count %d", windows.count); NSLog(@"Window has Resign keyWindow: %@, window level: %f, index of windows: %d", window, window.windowLevel, [windows indexOfObject:window]); } - (void)windowBecomeVisible:(NSNotification*)noti { UIWindow *window = noti.object; NSArray *windows = [UIApplication sharedApplication].windows; NSLog(@"current window count %d", windows.count); NSLog(@"Window has become visible: %@, window level: %f, index of windows: %d", window, window.windowLevel, [windows indexOfObject:window]); } - (void)windowBecomeHidden:(NSNotification*)noti { UIWindow *window = noti.object; NSArray *windows = [UIApplication sharedApplication].windows; NSLog(@"current window count %d", windows.count); NSLog(@"Window has become hidden: %@, window level: %f, index of windows: %d", window, window.windowLevel, [windows indexOfObject:window]); } @end
iOS 6:
a、当我们打开viewDidAppear中“[self presentAlertView];”的时候,控制台输出如下:
根据打印的信息我们可以看出流程如下:
1、程序默认的window先显示出来
2、默认的window再变成keyWindow
3、AlertView的window显示出来
4、默认的window变成非keyWindow
5、最终AlertView的window变成keyWindow
总体来说就是“要想当老大(keyWindow),先从小弟(非keyWindow)开始混起” 而且根据打印的信息我们同事可以知道默认的window的level是0,即normal级别;AlertView的window的level是1996,比Alert级别稍微低了一点儿。
b、当我们打开viewDidAppear中“[self presentActionSheet];”的时候,控制台输出如下:
keyWindow的变化和window的显示和上面的流程一样,同时我们可以看出ActionSheet的window的level是2001。
c、接着上一步,我们点击弹出ActionSheet的cancel的时候,控制台输出如下:
我们看出流程如下:
1、首先ActionSheet的window变成非keyWindow
2、程序默认的window变成keyWindow
3、ActionSheet的window在隐藏掉
总体就是“想隐居幕后可以,但得先交出权利”。
iOS7
2014-10-29 15:57:59.922 TestWindowLevel[44895:60b] current window count 1
2014-10-29 15:57:59.924 TestWindowLevel[44895:60b] Window has become visible: <UIWindow: 0x8c56810; frame = (0 0; 320 568); gestureRecognizers = <NSArray: 0x8c58f90>; layer = <UIWindowLayer: 0x8c57650>>, window level: 0.000000, index of windows: 0
2014-10-29 15:57:59.925 TestWindowLevel[44895:60b] current window count 1
2014-10-29 15:57:59.925 TestWindowLevel[44895:60b] Window has become keyWindow: <UIWindow: 0x8c56810; frame = (0 0; 320 568); gestureRecognizers = <NSArray: 0x8c58f90>; layer = <UIWindowLayer: 0x8c57650>>, window level: 0.000000, index of windows: 0
2014-10-29 15:58:00.071 TestWindowLevel[44895:60b] current window count 1
2014-10-29 15:58:00.072 TestWindowLevel[44895:60b] Window has Resign keyWindow: <UIWindow: 0x8c56810; frame = (0 0; 320 568); gestureRecognizers = <NSArray: 0x8c58f90>; layer = <UIWindowLayer: 0x8c57650>>, window level: 0.000000, index of windows: 0
2014-10-29 15:58:00.073 TestWindowLevel[44895:60b] current window count 1
2014-10-29 15:58:00.073 TestWindowLevel[44895:60b] Window has become keyWindow: <_UIModalItemHostingWindow: 0x8c1fc40; frame = (0 0; 320 568); alpha = 0; hidden = YES; gestureRecognizers = <NSArray: 0x8c59190>; layer = <UIWindowLayer: 0x8c59600>>, window level: 0.000000, index of windows: 2147483647
2014-10-29 15:58:00.081 TestWindowLevel[44895:60b] current window count 1
2014-10-29 15:58:00.145 TestWindowLevel[44895:60b] Window has become visible: <_UIModalItemHostingWindow: 0x8c1fc40; frame = (0 0; 320 568); alpha = 0; gestureRecognizers = <NSArray: 0x8c59190>; layer = <UIWindowLayer: 0x8c59600>>, window level: 0.000000, index of windows: 2147483647
1、iOS6和iOS7中苹果对UIAlert和UIActionSheet的实现有所不同。
2、UIApplication中windows 在iOS6和iOS7中有区别。
iOS6:
This property returns an array of the application‘s visible and hidden windows. The windows are ordered back to front.
iOS7:
This property contains the UIWindow objects currently associated with the app. This list does not include windows created and managed by the system, such as the window used to display the status bar.(当然也不包括UIAlertView和UIActionSheet).
The windows in the array are ordered from back to front by window level; thus, the last window in the array is on top of all other app windows.