iOS View 编程指导(二)-window

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时需要注意一下几点:

  1. 在runtime访问window,需要先给xib中的window弄一个outlet,一般和AppDelegate文件绑定
  2. 同时需要在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

UIWindowDidBecomeVisibleNotificationUIWindowDidBecomeHiddenNotification会在当你使用代码控制window的hide/show时,然而当你应用进入后台时,不会发送通知,因为系统认为window是可见的.

UIWindowDidBecomeKeyNotificationUIWindowDidResignKeyNotification这两个通知帮你开发者确定那个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,内容和主屏上的一致.

外接屏幕显示内容的步骤:

  1. APP启动时,注册screen conection和disconnection通知
  2. 当开始显示外屏时,需要创建和配置一个window
    • 使用UIScreen的属性screens获取外部屏幕的screen
    • 创建一个UIWindow对象,然后根据上面的screen来设置size
    • 将上面的screen赋值给window的screen属性
    • 调整screen的分辨率以支持你要显示的内容
    • 往window中添加内容
  3. 将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

你可能感兴趣的:(iOS View 编程指导(二)-window)