View编程指南3—Windows

系统化学习,知其然,知其所以然

每个 iOS 应用程序至少需要一个窗口, 有些可能包含多个窗口。一个窗口对象有几个职责:

  1. 作为一个容器包含应用程序的可见内容。
  2. 将触摸事件传递到视图和其他对象过程中起着关键作用。
  3. 和视图控制器协作,处理设备旋转方向。

在iOS中,Windows没有标题栏,关闭框或其他视觉装饰。一个窗口始终只是一个或多个视图的空白容器。此外,应用程序不会通过显示新窗口来更改其内容。如果要更改显示的内容,请改为改变窗口的最前面的视图。

大多数iOS应用程序在其生命周期中只创建并使用一个窗口。该窗口占据设备的整个主屏幕,并在应用程序生命周期的早期从应用程序的主要 storyboard/nib 文件(或以编程方式创建)加载。但是,如果应用程序支持使用外部显示器进行视频输出,则可以创建一个额外的窗口来在该外部显示器上显示内容。所有其他窗口通常由系统创建,通常是为了响应特定事件(例如来电)创建的。

一、关于Windows的作用

对于许多应用程序,APP 与窗口交互的唯一时间是在启动时创建窗口的时间。 但是,可以使用窗口对象来执行其他任务:

  1. 进行坐标转换。

  2. 使用窗口通知来跟踪与窗口相关的更改。 Windows会在显示或隐藏通知或者接受或退出密钥状态时生成通知。 您可以使用这些通知在应用程序的其他部分执行操作。 有关更多信息,请参阅监视窗口更改。

二、Window的创建和设置

创建Window有两种方式: 编程方式和Interface Builder。有以下注意点

  1. 在启动时创建窗口,保留该窗口并将其引用存储在AppDelegate中。
  2. 如果创建额外的窗口,让应用程序在需要时创建它们。 例如,支持在外部显示器上显示内容,则应在显示器连接成功后创建相应窗口。
  3. 无论应用程序是启动到前台还是后台,都应始终在启动时创建应用程序的主窗口。
  4. 如果应用程序直接进入后台,则应避免在应用程序进入前台之前显示窗口。

2.1 创建方式一:Interface Builder

分为2种使用场景:新工程和旧工程

  • 新建工程,Xcode的工程模板会自动完成创建工作,文件命为 Main.storyboard(不同时期名称不同)。
  • 从代码迁移到 Interface Builder ,除了拖拽一个 Window 之外,还需要完成以下操作
    • 想在运行时访问,一般做法是创建一个引用保存到 AppDelegate 对象中
    • 修改 Info.plist 文件中 UIMainStoryboardFile 的值,以便AppDelegate调用- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions;时,Main.storyboard 已经加载完毕。

2.2 创建方式二:代码创建

//首先,在AppDelegate中创建一个引用,方便运行时访问
@property (strong, nonatomic) UIWindow *window;

//然后创建对象
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    //注意一定要全屏尺寸
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] ;
    return YES;
}

2.3 添加内容

通过

- (void)addSubview:(UIView *)view;

添加view,为了方便更换显示内容,使用一个rootView,其他view添加到rootView,更换显示内容直接更换rootView即可,记得适配设备屏幕尺寸。

此外,Window 提供了一个rootViewController属性

@property(nonatomic, strong) UIViewController *rootViewController;

UIKit 自动使用其view作为 window 的 rootView ,并且自动适配设备屏幕尺寸。

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] ;
    
    ViewController *rootViewController = [[ViewController alloc] init];
    
    self.window.rootViewController = rootViewController;
    
    return YES;
}

三、监测 Window 状态变化

可以通过以下通知来追踪状态变化

//显示
UIWindowDidBecomeVisibleNotification
//隐藏
UIWindowDidBecomeHiddenNotification
//关键窗口
UIWindowDidBecomeKeyNotification
//非关键窗口
UIWindowDidResignKeyNotification
  • 显示或隐藏一个窗口,传递UIWindowDidBecomeVisibleNotification和UIWindowDidBecomeHiddenNotification通知。当应用程序进入后台执行状态时,不会传递这些通知。即使窗口在应用程序处于后台时并未显示在屏幕上,但在应用程序的上下文中,它仍被视为可见。

  • UIWindowDidBecomeKeyNotification和UIWindowDidResignKeyNotification通知帮助识别哪个窗口是关键窗口(当前正在接收键盘事件和其他非触摸相关的事件)。触摸事件传递到发生触摸的窗口,而没有相关坐标值的事件被传递到的关键窗口。KeyWindow 只有一个。

四、在外部显示器上显示内容(Displaying Content on an External Display)

要在外部显示器上显示内容,必须为应用程序创建一个额外的窗口,并将其与代表外部显示器的屏幕对象相关联。一旦窗口与正确的屏幕相关联,您可以添加视图并显示它,就像您为应用程序的主屏幕一样。

UIScreen维护一个代表可用硬件显示的屏幕对象列表。通常,只有一个屏幕对象表示任何基于iOS的设备的主显示屏,但是支持连接到外部显示屏的设备可以具有可用的附加屏幕对象。支持外部显示的设备包括具有Retina显示屏和iPad的iPhone和iPod touch设备。较旧的设备(如iPhone 3GS)不支持外部显示器。

注意:由于外部显示本质上是一个视频输出连接,因此没有触摸事件。另外,应用程序负责根据需要更新窗口的内容。因此,要镜像主窗口的内容,应用程序需要为外部显示器的窗口创建一个重复的视图,并保持同步更新。

可以使用 AirServer 软件在 Mac 电脑上模拟一个支持 AirPlay 的屏幕

基本过程如下

  1. 在应用程序启动时,注册屏幕连接和断开通知。

  2. 当在外部显示器上显示内容时,请创建并配置一个窗口。

    1. 通过UIScreen的 screens 属性获取外部显示的屏幕对象。
    2. 创建一个UIWindow对象,并根据屏幕(或内容)适当地调整它的大小
    3. 将外部显示的UIScreen对象分配给窗口的screen属性
    4. 根据需要调整屏幕对象的分辨率以支持您的内容
  3. 显示并正常更新窗口。

4.1 处理屏幕连接和断开通知(Handling Screen Connection and Disconnection Notifications)

@interface ViewController ()
{
    UIWindow *_secondWindow;
}
@end

- (void)viewDidLoad {
    [super viewDidLoad];

    [self setupScreenConnectionNotificationHandlers];
}

//注册通知
- (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];
}

- (void)handleScreenConnectNotification:(NSNotification*)aNotification
{
    UIScreen*    newScreen = [aNotification object];
    CGRect        screenBounds = newScreen.bounds;
 
    if (!_secondWindow)
    {
        _secondWindow = [[UIWindow alloc] initWithFrame:screenBounds];
        _secondWindow.screen = newScreen;
 
        // 初始化 UI
        [viewController displaySelectionInSecondaryWindow:_secondWindow];
    }
}
 
- (void)handleScreenDisconnectNotification:(NSNotification*)aNotification
{
    if (_secondWindow)
    {
        // 隐藏并移除window.
        _secondWindow.hidden = YES;
        _secondWindow = nil;
 
        // 同步主屏幕和外接屏幕显示内容
        [viewController displaySelectionOnMainScreen];
    }
 
}

4.2 配置外部显示器的窗口(Configuring a Window for an External Display)

- (void)checkForExistingScreenAndInitializeIfPresent
{
    if ([[UIScreen screens] count] > 1)
    {
        // 关联 window 和 second screen.
        // 主屏幕 index 0.
        UIScreen*    secondScreen = [[UIScreen screens] objectAtIndex:1];
        CGRect        screenBounds = secondScreen.bounds;
 
        _secondWindow = [[UIWindow alloc] initWithFrame:screenBounds];
        _secondWindow.screen = secondScreen;
 
        // 添加一个 rootView 到 window
        UIView*            whiteField = [[UIView alloc] initWithFrame:screenBounds];
        whiteField.backgroundColor = [UIColor whiteColor];
 
        [_secondWindow addSubview:whiteField];

        // 中间放一个 label.
        NSString*    noContentString = [NSString stringWithFormat:@"<no content>"];
        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];
 
        // 显示 window.
        _secondWindow.hidden = NO;
    }
}

4.3 配置外部显示器的屏幕分辨率(Configuring the Screen Mode of an External Display)

根据显示内容,可以在将窗口与屏幕关联之前更改屏幕分辨率。 许多屏幕支持多种分辨率,使用不同的长宽比。屏幕支持的分辨率可以从 UIScreen 的 availableModes 属性获取屏幕支持的分辨率列表

@property(nonatomic, readonly, copy) NSArray<UIScreenMode *> *availableModes;

选择合适的分辨率然后设置

@property(nonatomic, strong) UIScreenMode *currentMode;

有关屏幕分辨率的更多信息,请参阅UIScreenMode参考。

参考文章

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

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,357评论 25 707
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,579评论 18 139
  • 问答题47 /72 常见浏览器兼容性问题与解决方案? 参考答案 (1)浏览器兼容问题一:不同浏览器的标签默认的外补...
    _Yfling阅读 13,722评论 1 92
  • 我想去找一盏灯, 灯是什么? 是东方升起的一道火光, 是天边一个火红的斑点, 是天空中的万道金光。 我想去找一把伞...
    小学生朱提提阅读 170评论 0 6
  • 我愣愣地望着那人,他依旧如三年前一般,白衣翩翩,坐在木轮椅上,还是他那个通晓堂的“玉公子”。 眼前的画面突然被一双...
    执年就是依晗曦阅读 144评论 0 0