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。因此大家還是需要引起注意的!

發佈了75 篇原創文章 · 獲贊 70 · 訪問量 10萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章