UIWindow

UIWindow
Multiple Display Programming Guide for iOS

一个UIWindow对象是iOS应用程序中界面元素的容器,虽然UIWindow继承自UIView,但UIWindow本身并不作为界面展示,一个应用中可以有多个UIWindow对象。此外,UIWindow还是事件处理的重要组成部分,当应用最初收到事件时,会派发给合适的UIWindow对象,再由其传递到合适的UIView对象。同时,window和view controller对象一起工作还可以实现界面旋转等其他控制操作。

Main Window

通常情况下应用只需要一个主窗口来显示应用的内容到屏幕上。主窗口的创建可以通过Storyboard初始化或者代码中两种方式创建:

  1. 通过Storyboard初始化界面时(在Info.plist文件中指定Main storyboard file base name),系统隐含了一些过程,首先初始化一个window对象,设置该window对象的screen属性为[UIScreen mainScreen],然后初始化storyboard中的view controller,并将其赋给window对象的rootViewController属性。同时,需要应用代理中有一个window属性,系统会把window对象赋值给它。注意,应用代理中的window属性是必须的,否则界面无法显示,系统会提示The app delegate must implement the window property if it wants to use a main storyboard file,其实原因就是如果没有一个成员变量来维持对window对象的引用,那么window对象就无法一直存在了。最后在执行应用代理的初始化函数application: didFinishLaunchingWithOptions:时,window并没有显示,这意味着在window显示前,我们有机会去修改rootViewController。在这之后,window对象会变成应用的keyWindow并显示出来。
  2. 也可以通过代码来创建window初始化界面
- (BOOL)application:(UIApplication *)application willFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    UIWindow *window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    myViewController = [[MyViewController alloc] init];
    window.rootViewController = myViewController;
    [window makeKeyAndVisible];

    return YES;
}  

如果在xib文件中拖入一个window,并通过其实例化window,一定要在属性设置中勾选Full Screen at Launch

- (BOOL)application:(UIApplication *)application willFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    NSArray *viewArray = [[NSBundle mainBundle] loadNibNamed:@"Window" owner:nil options:nil];
    self.window = viewArray.firstObject;
    UIViewController *viewController = [[UIStoryboard storyboardWithName:@"Main" bundle:[NSBundle mainBundle]]instantiateInitialViewController];
    self.window.rootViewController = viewController;
    [self.window makeKeyAndVisible];
    
    return YES;
}

rootViewController

rootViewController属性代表了当前展示的界面,尽管可以将view直接加到window上,但正确的方式应该是指定window的rootViewController,当设置rootViewController之后,window会自动将view controller的view添加到window中,并设置到合适的尺寸,这样可以通过改变rootViewController来方便的切换界面。除了使用UIViewController和自定义的controller外,通常还使用navigation, tab bar, and split view controller作为rootViewController。切换rootViewController时,实际上是旧的controller的view从window中删掉,再将新的controller的view加进window中。此外,content view的尺寸随window的尺寸改变(即设备旋转),也必须通过rootViewController才能实现。该值默认为nil。

windowLevel

该属性表示了window在屏幕z轴方向上的位置,系统提供了UIWindowLevel的三个常量值

UIWindowLevelNormal
UIWindowLevelAlert
UIWindowLevelStatusBar

显示应用内容的main window默认设置为UIWindowLevelNormal,即最低的level。其他一些界面元素比如status bar和alert view会使用相应的level。windowLevel也可以设置为其他的任意数值,比如系统键盘所在的window,其windowLevel总是比调用键盘的window的level大1,选中输入内容时的拷贝,粘贴等选项所在的window其windowLevel是2100。可以多个window设置为同一个windowLevel,在这些window中,最后调用显示方法的window排在上面。不同level的window组中,总是更高level的window在低level的window的上面。

screen

该属性表示显示window的UIScreen对象,默认值是主屏对象。切换window的屏幕是非常消耗资源的操作,所以应在显示window前设置好其对应的screen对象。

keyWindow

该属性指明window是否为keyWindow。整个App中只能有一个keyWindow,keyWindow接收键盘事件和所有的非触摸事件。大部分情况下,应用的main window就是keyWindow,但其他window也可以变为keyWindow,比如另一个window中有UITextField等input view,当这个window显示出来,并点击input view进行编辑,此时该window会自动切换为keyWindow,并弹出键盘。注意:该属性是只读的。

- makeKeyAndVisible

该方法让window显示并变为keyWindow。调用该方法会让window排在其level组中的最上面。如果只想改变window的显示而不影响keyWindow状态,可以直接设置window的hidden属性为NO。

- makeKeyWindow

该方法使window变为keyWindow,但不影响显示状态。

- becomeKeyWindow

该方法不应该被手动调用,当window变为keyWindow时会被自动调用来通知window。可以继承UIWindow重写此方法来实现功能。

- resignKeyWindow

该方法不应该被手动调用,当window不再是keyWindow时(例如其他window实例调用了- makeKeyWindow或- makeKeyAndVisible方法)会被自动调用来通知window。可以继承UIWindow重写此方法来实现功能。

- sendEvent:

UIApplication调用window的该方法给window分发事件,window再将事件分发到合适的目标,比如将触摸事件分发到真正触摸的view上。可以直接调用该方法分发自定义事件。

相关通知

// Window显示和keyWindow状态改变的通知,object是window本身,没有userInfo字典。
// 应用的显示和挂起不会触发window显示状态改变的通知。
UIWindowDidBecomeVisibleNotification
UIWindowDidBecomeHiddenNotification
UIWindowDidBecomeKeyNotification
UIWindowDidResignKeyNotification
// keyboard变化的通知,没有object,有userInfo字典。
UIKeyboardWillShowNotification
UIKeyboardDidShowNotification
UIKeyboardWillHideNotification
UIKeyboardDidHideNotification
UIKeyboardWillChangeFrameNotification
UIKeyboardDidChangeFrameNotification
// keyboard通知userInfo字典key值。
NSString *const UIKeyboardFrameBeginUserInfoKey;
NSString *const UIKeyboardFrameEndUserInfoKey;
NSString *const UIKeyboardAnimationCurveUserInfoKey;
NSString *const UIKeyboardAnimationDurationUserInfoKey;
NSString *const UIKeyboardIsLocalUserInfoKey;

坐标转换

对应UIView,UIWindow提供了四个坐标转换的方法,官方建议每一个screen对应一个展示内容的main window,window的frame应与screen一样,所以暂时没有想到应用这些方法的例子。其中window参数如果传入nil,那么就以screen的坐标系统代替。

- convertPoint:toWindow:
- convertPoint:fromWindow:
- convertRect:toWindow:
- convertRect:fromWindow:

外接屏幕显示window

可以通过AirPlay等方式将其他屏幕连接到iOS设备,这时iOS默认将设备上的界面镜像到外接屏幕上,而我们也可以再创建一个UIWindow实例添加到新的屏幕上来展示一些不一样的内容。首先我们要判断是否有外接屏幕,如果有的话,则创建新的window,并注册通知监听屏幕的变化,当外部屏幕链接断开后,则注销对应的window。外部屏幕的连接与断开可以发生在应用生命周期的任何时候,所以适合在AppDelegate中监听,当应用挂起时,相应的通知会排入队列中,直到应用重新运行(包括后台运行)时推送。

@interface AppDelegate ()

@property (nonatomic) UIWindow *anotherWindow;

@end

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    [self checkForExistingScreenAndInitializeIfPresent];
    [self setUpScreenConnectionNotificationHandlers];
    
    return YES;
}

- (void)checkForExistingScreenAndInitializeIfPresent {
    if ([[UIScreen screens] count] == 1) {
        return;
    }
        
    UIScreen *secondScreen = [UIScreen screens][1]
    [self showAnotherWindowInScreen:secondScreen];
}

- (void)setUpScreenConnectionNotificationHandlers {
    NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
    [center addObserver:self selector:@selector(handleScreenDidConnectNotification:) name:UIScreenDidConnectNotification object:nil];
    [center addObserver:self selector:@selector(handleScreenDidDisconnectNotification:) name:UIScreenDidDisconnectNotification object:nil];
}

- (void)handleScreenDidConnectNotification:(NSNotification*)aNotification {
    UIScreen *newScreen = [aNotification object];
    [self showAnotherWindowInScreen:newScreen];
} 

- (void)handleScreenDidDisconnectNotification:(NSNotification*)aNotification {
    if (self.anotherWindow) {
        // Hide and then delete the window.
        self.anotherWindow.hidden = YES;
        self.anotherWindow = nil; 
    } 
}

- (void)showAnotherWindowInScreen:(UIScreen *)screen {
    CGRect screenBounds = screen.bounds;
    self.anotherWindow = [[UIWindow alloc] initWithFrame:screenBounds];
    self.anotherWindow.screen = screen;
    UIStoryboard *mainStoryboard = [UIStoryboard storyboardWithName:@"Main" bundle:[NSBundle mainBundle]];
    ContentViewController *contentViewController = [mainStoryboard instantiateViewControllerWithIdentifier:@"ContentViewController"];
    self.anotherWindow.rootViewController = contentViewController;
    self.anotherWindow.hidden = NO;
}

应用启动时通过代理方法处理,之后通过监听通知处理。
注意:要在window显示之前,设置其对应的screen,否则修改正在显示的window的screen会造成很大的性能损耗。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271

推荐阅读更多精彩内容