01.the window

The Window

作者:PMST
文章:Views - Window
系列:The Swift Beginner
写于:2015.04.25

该系列更多文章详见个站Colourful Code

正文

前言讲到App由多个 Views 构建显示而成,称之为视图层级(view hierarchy),那么不禁要问,它的最顶层是什么?答案是: Window。 一个通过 UIWindow类 或者继承 UIWindow的子类实例,特别要提出的是 UIWindow 是继承自 UIView 。Ps:你可以找个项目打开AppDelegate.swift文件,其中有个var window:UIWindow?变量声明,option+鼠标左击 UIWindow,弹出关于它的信息,其中 “Declaration class UIWindow : UIView” 证明了前面所说。

Ok,关于App,确切地说只有一个主窗口(main window),在launch的时候创建,此后你无法对它销毁或者替换。主窗口会占据整个窗口,将自身作为整个背景(background),这个很好理解,因为我们看的确实如此。相对于其他所有可见的视图,main window 自然就是superView喽,其他视图作为subView呈现自身内容,这点是需要记住的。

Window 必须要填充满整个设备的屏幕,意味着它的尺寸大小和放置位置要和屏幕的尺寸和位置相统一。还记得前面提到创建的UIWindow实例吗,通过设置它(实例,通常取名为:window)的frame属性等于屏幕(Screen)的bounds即可,对于framebounds 这两个属性,会在之后的一节进行解释。关于设置问题我们从两种情况出发,使用storyboard 和使用代码。

  • 使用 a main storyboard ,在app launch之前,它会非常贴心地为你完成这些繁琐的设置,比如尺寸、放置位置等等。

  • 使用代码的情况下, app 要求我们去手写代码设置 windowframe,就像这样let w = UIWindow(frame:UIScreen.mainScreen().bounds)。假如你有编程基础,解读起来一定很容易;如果是初学者也没有关系,仔细看这行代码,和我前面所说是一样的,构造一个frame和屏幕bounds一样的实例窗口(window)。

值得一提的是,在你应用程序的生命周期里(lifetime,这个概念比较重要),先前window实例将一直存在。为了达到这个目的,应用的delegate类中有一个window属性,采用强引用(strong retain)指向先前的window实例。具体是这么实现的:当应用程序启动时,UIApplication函数实例化一个代理(delegate)并且将生成结果保持住,对于app delegate来说,它永远不会被释放(released),在应用程序的生命周期里将一直存在;接着,window实例会被signeddelegate中的window属性,采用强引用的方式,意味着在app的整个生命周期,只要delegate不消亡,那么它之中的window属性也不回消亡。

补充:第一点:window实例如何signeddelegate中的window,很简单,就类似代码中的赋值操作,有点像delegate.window = w,w是先前生成的窗口实例。第二点:对于强引用,也就是strong reference,首先对于类之间的赋值,不是简单的值拷贝方式,而是采用引用方式,即都指向一个对象,其次是strong关系,必然是在两个对象之间发生,只要强引用一方不解除关系,另外一方就要一直存在。

要呈现app内容,将所有视图内容手工地、直接地放入主窗口(main window)?Say NO!,请你最好马上现在摒弃这种思想,相应地,获得一个View Controller 即视图控制器,然后分配给主窗口(main window实例)中的rootViewController属性。不得不再次提及,假如你使用SB的话,这些设置都是在背后悄悄帮你完成的;而view controller则将成为storyboard的初始控制器(initial view controller),有些细心的朋友已经在SB中发现有一个箭头标志,其实右侧面板对应一个属性设置check框,用来指派是否成为初始控制器(Is Initial View Controller)

当视图控制器成为了主窗口(main window实例)的rootViewController之后,该视图控制器中的主视图(main view)立即成为主窗口的subview——当然也就是主窗口的root view。而其他在窗口中的视图将会成为该root viewsubview。貌似有点绕口,但是如果你多看几遍,便会发现很好理解。有时候我们好奇在root view 下的 window “过得怎样”,抱着该目的会设置main window的背景颜色(backgroundColor)。这里有个状况,我再测试时候两个工程,一个设置了背景却没有效果,另一个却是红色的,我抽空会再确认下!

凡事都是需要一个过程,应用程序的视图也不会立即呈现,直到将window包含进来,且设置成为appkey window。代码设置即调用UIWindow的实例方法makeKeyAndVisible,从方法名就能解读其作用。

ok,是时候总结一下初始化时所要做的创建、配置以及如何将window显示出来。下面从使用storyboard 和未使用storyboard两种情况来讲解:

使用a main storyboard的情况

打开项目,找到Info.plist文件中键值为“Main storyboard file base name”,值为”Main”,对应我们项目中的Main.storyboard。值得一提的是对于所有Xcode 6 app都是默认如此。接下来要说的是,UIApplicationMain(你可以在appDelegate.swift文件找到)中会实例化一个UIWindow(如果你没有自定义window,内部自动构造一个实例),正确设置frame属性,然后赋值给app delegate中的window变量属性(也就是 lazy var window : UIWindow?window,引用关系),与此同时,它还会实例化storyboard中的view Controller(通过SBIdentity Inspector看到,class下看到是ViewController,这是默认的),然后分配给window实例的rootViewController属性,这些都是后台执行,你看不到的。The one more thing,所有这一切都发生在delegateapplication:didFinishLaunchingWitchOptions:调用之前!!!!最后,UIApplicationMain 调用makeKeyAndVisible方法使得我们的window摆正位置,呈现应用界面。之后轮到root view controller去获得main view(通常我们会从nib文件中加载得到),将主视图作为windowroot view。这个过程发生在application:didFinishLaunchingWitchOptions:调用之后!!

未使用a main storyboard的情况

这对于用惯了storyboard的朋友无疑是“新大陆”,创建和配置window方式也从可视化往纯代码发展。假如想要创建一个无storyboard的工程,很遗憾,在Xcode 6 app中是没有的,都是默认创建一个main storyboard,可见苹果公司是支持并鼓励用SB结合代码方式来完成项目。因此我们只能在Single View Application模板上进行修改达到我们所要的目的。你可能需要动手完成以下步骤。

  1. 打开项目,选中你的target(蓝色工程文件.xcodeproj),在General面板找到Main Interface,你会看到默认是Main,删除它,回车或tab键确认。
  2. 删除main.storyboard文件和ViewController.swift文件。
  3. 删除AppDelegate.swift中原先的代码,全部删掉都ok。

如此就得到是一个没有storyboard和任何代码的空项目。现在我们添加一些必要代码使其变成一个能够运行的最小应用。首先选中AppDelegate.swift文件,写入必要代码(请见下)来实现创建window以及显示window。为了让证明我们的的确确创建了一个window,你可以使用设置其背景颜色为红色(UIColor.redColor())。由于没有设置一个root view controller 因此我们的调试区域会给出警告“Application windows are expected to have a root view controller at the end of application launch”。别紧张,“It’s Ok!” 接下来的文章便会告诉你如何实现。

import UIKit
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

  var window: UIWindow?

  func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {

    self.window = UIWindow(frame: UIScreen.mainScreen().bounds)
    self.window!.backgroundColor = UIColor.redColor()
    self.window!.makeKeyAndVisible() 

    return true
  }
}

以上就是一个能运行的最小应用了,你可以尝试在模拟器运行,会看到红色的一个界面,它就是window

一般情况下我们这么实现就已经OK了,但是假如你玩性大发,较真地想要自定义一个window,通过继承UIWindow创建其subclass,然后实例化一个自定义window作为应用的主窗口。既然讲了,那么就讲到底,这里也分两种情况。

自定义创建window下,使用a main storyboard的情况

首先应用程序启动,在UIApplicationMain实例化了一个delegate之后,app会向delegate索要它当中的window属性(前文已经详细讲过),假如值不存在(nil),那么UIApplicationMain就会自动实例化一个,然后将生成实例分配给window属性变量;如果值是存在的,那么UIAplicationMain就随他去喽,就按照那个存在的window作为应用的主窗口。所以,假如我们我们构造了一个UIWindow subclass的实例作为应用的主窗口,那么自然还缺少一步:将生成的实例分配给delegate当中的window属性。代码实现:

lazy var window:UIWindow = {
    return MyWindow(frame:UIScreen.mainScreen().bounds)
}

没了?……抱歉我想是的!至于其他的就交给后台去做吧!至于MyWindow就需要你自己新建一个Cocoa class文件,命名MyWindow,强调它是继承自UIWindow的!

自定义创建window下,不使用a main storyboard的情况

作为代码实现,想想最最前面我们创建的那个没有使用SB的例子,做的很好!可惜使用的是UIWindow,现在我们想要使用MyWindow,又该如何做呢?首先实例化一个自定义MyWindow然后分配给delegate当中的self.window属性。就像这样:

self.window = MyWindow(frame:UIScreen.mainScreen().bounds)

获取window的方式

一旦应用运行,这里存在两种方式去获得window

  • 如果当前界面中显示了某个UIView,那么该view自动产生一个引用指向window,换句话说该view中存在一个window属性!!调用方式自然就是view.window。不过这里不得不遗憾地告诉你,如果当前的view没有现在在window中,那么它的window属性是等于nil的!!聪明的朋友已经意识到了,那么反过来说如果一个viewwindow属性等于nil,这个视图就不可能在窗口中呈现!

  • 前面总是说app delegate始终保持了一个对window的强引用。因此我们采用迂回战术,通过shared application‘s delegate属性获得delegate的引用,然后通过它简介获得window。代码是这样的:

    let w = UIApplication.sharedApplication().delegate!.window
    //假如你觉得这个隐式解包麻烦 可以那么写
    let w = (UIApplication.sharedApplication().delegate as AppDelegate).window
  • 还有一种获得机制是因为shared application 通过keyWindowwindow进行了引用保持,至于原因前面已经说过,代码是这样实现的。
    let w = UIApplication.sharedApplication().keyWindow

不过呢,第三种引用稍微有个问题,因为系统有时候会暂时生成一个window,然后将其插入到应用key window。因此大家还是需要引起注意的!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值