Windows
iOS应用中window是UIWindow
的实例,而且应用中至少需要一个window. window在iOS的作用有:
- 用来放置应用中显示的内容
- 在事件传递过程中起到重要作用
- 帮助controller处理设备屏幕旋转事件
在iOS中,Window本身不能显示,它是用来放置其他可见view的容器. 一个应用需要更新内容时,通常是更新window中的view,而不是将window也更新. 在iOS应用中的生命周期中,大多数时候都是只有一个window,只有少数情况下回创建新的window; 在设备接入了位置屏幕时,系统才会创建第二个window,用来显示位置屏幕的内容; 突然收到电话时,系统也会创建另外一个window.
能用window干些什么
对大多数APP来说,只有在APP启动创建window时,才会和window进行交互. 但是,你还能使用window做一些其他应用级别的任务:
- 使用window对象可以将一个point和CGRect转换到window中的坐标系下,或者转换到其他view中的坐标系中.具体如何转换下文将会讲解.
- 监听window相关的通知来获取window的改变. 当window隐藏(hidden)/显示(shown)/成为或注销keyWindow时,window都会发送通知,在应用中你可以监听这些通知来处理一些问题.
window的创建和设置
window创建可以通过代码或者XIB. 在启动时创建window并用AppDelegate的一个属性引用它. 如果你需要创建额外的window请使用懒加载,比如你的设备要在外设显示,创建window应该在确认外设已经连接好开始准备显示内容之后.
main window的创建总在APP启动时,无论APP启动进入后台还是前台. 虽然创建和配置window不是一个耗性能的操作,但如果APP启动后进入后台的话,那么你不应该直接makeKeyAndVisible,应该等APP进入前台时才makeVisible
使用XIB创建window
在创建xcode APP项目时,xcode会使用xib自动给你创建一个window,并且成为了AppDelegate的outlet属性. 使用Xib创建window时需要注意一下几点:
- 在runtime访问window,需要先给xib中的window弄一个outlet,一般和AppDelegate文件绑定
- 同时需要在info.plist或者配置build setting来确保nib文件在APP调用
application:didFinishLaunchingWithOptions:
时正确加载.
想看更多关于xib的知识请看Interface Builder User Guide, 想知道nib文件是怎样加载的话请看Resource Programming Guide中的Nib Files
注意:在使用xib创建window时,你应该在属性列表栏中(attributes inspector)将window设置为
Full Screen
. 因为如果你window不设置为全屏的话,可能会导致有些用户事件无法响应; 因为window区域外的touches event无法传递给window中的view,而window中的view超出了window区域外也可以显示,如果用户点击了window区域外的view产生的事件将无法通过window传递给里面的view.
使用代码创建Window
如果你想用代码创建main window可以在application:didFinishLaunchingWithOptions:
方法写下如下代码:
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
当手动创建window时,在AppDelegate中使用一个属性来保存它,如果连接外屏,此时需要创建第二个window同样在AppDelegate中要使用另外一个属性来保存这个非main window.
要牢记在设置window的frame时要充满整个屏幕,如果是外设的window,设置的frame要和外设的屏幕相匹配. 不需要考虑status bar等view, 另外status bar总是悬浮在window的上面,要考虑的是window中view的位置,因为可能被statusBar等view遮住.
往window中添加内容
每个window通常含有一个root view,该view一般是controller自带的view. root view中再包含其他要显示的view.使用root view的作用是能快速进行内容切换. window使用addSubview:
方法安装root view,如下所示:
[window addSubview:viewController.view];
在使用xib创建window的情况下,可以通过设置window的rootViewController
属性来到达上面代码同样的目的. 通过设置该属性,能够将controller的view安装到window中,成为root view. 所以rootViewController
的作用就是为了安装root view,不是将window和view controller连接在一起.
你可以使用系统提供的view,比如scroll view, table view, image view等成为window的root view; 也可使用自定义的view,这个根据跟人的需求.
安装完root view后,开发者需要负责设置root view的size和position. 如果你的应用没有status bar,或者status bar是透明的,那么root view应该充满整个屏幕. 如果应用中包含的status bar是不透明的,那么你应该减去status bar的高度,避免root view顶部内容被遮住.
注意:如果window的root view来自容器类视图控制器(如tab bar controller, navigation controller, splitView controller),这时你不必初始化root view的size,因为这些控制器会自动根据status bar的是否可见来设置root view的size.
改变Window Level
每个UIWindow对象都有个windowLevel
属性,该属性决定了windows间的位置关系,高等级window悬浮在低等级的上面. 绝大数情况,你都不需要管,因为系统帮你配置好了,配置是normal window level(用于显示应用级别的内容).如果系统级别的内容,比如收到了通知,状态栏等,系统也会自动配置为higher window level. 所以尽管你可以自己配置,但已经通过特殊接口帮你配置好了.
监听window的改变
通过监听下面的通知你可以知道到window的显示和隐藏:
- UIWindowDidBecomeVisibleNotification
- UIWindowDidBecomeHiddenNotification
- UIWindowDidBecomeKeyNotification
- UIWindowDidResignKeyNotification
UIWindowDidBecomeVisibleNotification和UIWindowDidBecomeHiddenNotification会在当你使用代码控制window的hide/show时,然而当你应用进入后台时,不会发送通知,因为系统认为window是可见的.
UIWindowDidBecomeKeyNotification和UIWindowDidResignKeyNotification这两个通知帮你开发者确定那个window是key window. key window是指可以接受键盘事件和其他非触摸事件, touch events传递给发送触摸事件的window,而那些和坐标没关系的事件则传递给key window. 同一时间内只有一个key window.
连接外置屏幕
想将内容显示在外设上,需要额外创建一个window,并将该window和代表外设的screen对象关联起来. 新创建的window默认和main screen关联. 修改和window关联的screen对象会导致window显示的内容显示在不同的屏幕上. 当window和screen对象关联起来后,就可以往window中添加内容.
类UIScreen
用一个List(通过代码[UIScreen screens]
获取)维护代表不同屏幕的screen对象.通常列表中只有一个代表iOS设备主屏的screen对象, 如果该iOS设备连接了外置屏幕,那么列表中会增加一个代表位置屏幕的screen对象. 拥有Retina屏幕的iPhone和iPod,ipad可以连接外置屏幕,老式iOS设备,比如iPhone 3GS不支持连接外屏幕.
注意:外接屏幕通常用来播放视频,所以显示在外接屏幕上的view和controls都不要加上手势,或者响应事件. 另外你需要负责更新外接屏幕上的内容更新,你可以保持两份view,内容和主屏上的一致.
外接屏幕显示内容的步骤:
- APP启动时,注册screen conection和disconnection通知
- 当开始显示外屏时,需要创建和配置一个window
- 使用
UIScreen
的属性screens
获取外部屏幕的screen - 创建一个UIWindow对象,然后根据上面的screen来设置size
- 将上面的screen赋值给window的
screen
属性 - 调整screen的分辨率以支持你要显示的内容
- 往window中添加内容
- 使用
- 将window显示然后正常更新
监听和处理Screen Connection 和Disconnection两个通知
这两个通知用来告诉你,什么时候该创建屏幕外window,什么时候销毁它.
因为系统发生通知的时机是不可以预的,所以用来监听该通知的对象需要一直存在,比如像AppDelegate这个对象; 即使APP挂起了,监听到通知后,AppDelegate会先缓存到队列中,等待APP进入前台或者后台执行后,AppDelegate会接着处理个通知.
下列代码显示AppDelegate在APP启动时注册监听通知,你可以在其他实际注册监听通知:
- (void)setupScreenConnectionNotificationHandlers
{
NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
[center addObserver:self selector:@selector(handleScreenConnectNotification:)
name:UIScreenDidConnectNotification object:nil];
[center addObserver:self selector:@selector(handleScreenDisconnectNotification:)
name:UIScreenDidDisconnectNotification object:nil];
}
当你iOS设备连接了外屏且APP收到了通知,你此时创建了一个window来显示在外屏上面; 如果你显示的内容创建时间比较长,你可以显示在window上显示一些默认内容,当内容创建好后在更新window.如果你创建的window如果没显示到外屏或者还没创建好window要显示的内容,外屏会一片漆黑.
下面代码展示了如何创建外屏window,和处理上面那两个通知的方法:
- (void)handleScreenConnectNotification:(NSNotification*)aNotification
{
UIScreen* newScreen = [aNotification object];
CGRect screenBounds = newScreen.bounds;
if (!_secondWindow)
{
_secondWindow = [[UIWindow alloc] initWithFrame:screenBounds];
_secondWindow.screen = newScreen;
// Set the initial UI for the window.
[viewController displaySelectionInSecondaryWindow:_secondWindow];
}
}
- (void)handleScreenDisconnectNotification:(NSNotification*)aNotification
{
if (_secondWindow)
{
// Hide and then delete the window.
_secondWindow.hidden = YES;
[_secondWindow release];
_secondWindow = nil;
// Update the main screen based on what is showing here.
[viewController displaySelectionOnMainScreen];
}
}
配置外屏window
显示外屏window在外屏时,需要将代表外屏的screen对象设置到window的screen
属性中, 该对象可以通过类UIScreen的属性screens
获取,该属性中保存了一个array,数组中至少有一个对象,表示主屏,如果数组中多了一个对象,则说设备连接了外屏.
下面代码展示了一个方法,用来在APP启动时检测是否连接了外屏,如果是则创建一个window,然后和外屏关联起来,而且在window中添加了placeholder以便在内容创建前显示在外屏中.要显示window可以通过hidden属性,而不是调用方法makeKeyAndVisible
,因为外屏window中只显示一些静态内容不响应用户事件.
- (void)checkForExistingScreenAndInitializeIfPresent
{
if ([[UIScreen screens] count] > 1)
{
// Associate the window with the second screen.
// The main screen is always at index 0.
UIScreen* secondScreen = [[UIScreen screens] objectAtIndex:1];
CGRect screenBounds = secondScreen.bounds;
_secondWindow = [[UIWindow alloc] initWithFrame:screenBounds];
_secondWindow.screen = secondScreen;
// Add a white background to the window
UIView* whiteField = [[UIView alloc] initWithFrame:screenBounds];
whiteField.backgroundColor = [UIColor whiteColor];
[_secondWindow addSubview:whiteField];
[whiteField release];
// Center a label in the view.
NSString* noContentString = [NSString stringWithFormat:@""];
CGSize stringSize = [noContentString sizeWithFont:[UIFont systemFontOfSize:18]];
CGRect labelSize = CGRectMake((screenBounds.size.width - stringSize.width) / 2.0,
(screenBounds.size.height - stringSize.height) / 2.0,
stringSize.width, stringSize.height);
UILabel* noContentLabel = [[UILabel alloc] initWithFrame:labelSize];
noContentLabel.text = noContentString;
noContentLabel.font = [UIFont systemFontOfSize:18];
[whiteField addSubview:noContentLabel];
// Go ahead and show the window.
_secondWindow.hidden = NO;
}
}
注意:在显示外屏前,你一定要将window和screen对象关联起来. 改变window的screen对象是非常耗性能的,尽量避免
当外屏window配置后,就可以像主屏一样任意添加内容,动画等操作
设置外屏的screen mode
可以通过screen对象的属性availableModes
来遍历可以用的UIScreenMode
对象,然后将它设定到screen的对象.具体请看UIScreenMode Class Reference