前言
這是斯坦福大學(xué)的課程-Developing iOS 9 APPs with Swift误甚,第6節(jié)課的內(nèi)容罗侯。主要是講:組合多個(gè) MVC (Multiple MVCs) 和 View Controller 的生命周期。在 Swift 學(xué)習(xí)筆記3中我也提到過一些 Multiple MVCs 的基礎(chǔ)知識(shí)涵叮,這篇文章算是應(yīng)用進(jìn)階版铅辞。
經(jīng)過這段時(shí)間的學(xué)習(xí)葵礼,個(gè)人總結(jié)了3個(gè)非常好的學(xué)習(xí) swift 基礎(chǔ)的途徑資料(列在下方)。其它的資料比如各路大神的書籍和博客我作為輔助學(xué)習(xí)迁沫,比如喵神芦瘾,YYKit 的作者 ibireme, 等等集畅。進(jìn)階的資料近弟,我覺得學(xué)習(xí) Github 中的各種開源項(xiàng)目是個(gè)非常好的選擇,比如喵神的 Kingfisher 等挺智。當(dāng)然如果大家能訂閱我的博客祷愉,我會(huì)非常開心噠。:)
下面是我用的最多的3個(gè) Swift 基礎(chǔ)學(xué)習(xí)資料:
- 斯坦福的這個(gè)課程,Developing iOS 9 Apps with Swift.
- 蘋果官方文檔二鳄,里面也有實(shí)例教程赴涵。Learn by doing 永遠(yuǎn)是最好的學(xué)習(xí)方法。
- 按住 option 點(diǎn)擊代碼中的任一單詞订讼。這個(gè)大家應(yīng)該都知道髓窜,但我是真心越來越覺得這個(gè)太好用了。
Demo
又是這張萌蠢的臉躯嫉,但這次它不僅換了發(fā)型纱烘,還添加了按鈕。感興趣的童鞋可以去我的 Github 查看源碼祈餐。
Segue
首先介紹一個(gè)名詞 - segue擂啥。簡短的翻譯就是:轉(zhuǎn)換。
我們創(chuàng)建了很多 Controller 的 Controller帆阳,我們需要用一個(gè) MVC 觸發(fā)另一個(gè) MVC哺壶,這種 MVC 之間的轉(zhuǎn)換就是 segue,一般不用 transition蜒谤,而用 segue山宾,之后會(huì)經(jīng)常遇到。
主要有4種 segue:
- Show Segue(比如在 Navigation Controller 中鳍徽,一個(gè) MVC 出現(xiàn)在另一個(gè) MVC 里资锰。)
- Show Detail Segue(比如我們這次的 Demo,一個(gè) master阶祭,一個(gè) detail绷杜,那么就需要用到這個(gè) show detail。Navigation Controller 中也可以使用 show detail segue濒募,和 show segue 功能一樣鞭盟。)
- Modal Segue(占據(jù)整個(gè)屏幕)
- Popover Segue(出現(xiàn)一個(gè)小彈窗)
Segue 會(huì)創(chuàng)建一個(gè)新的實(shí)例(后文也會(huì)繼續(xù)提到)。就是說每次點(diǎn)擊同樣的按鈕瑰剃,它返回的不是之前的那個(gè)界面齿诉,雖然長的也許一樣,但其實(shí)是一個(gè)全新的界面晌姚,是重新創(chuàng)建的粤剧。
必須要給新建的 segue 一個(gè) Identifier(在 Attributes inspector 中設(shè)置)。每個(gè) segue 都要有自己獨(dú)特的 id挥唠,因?yàn)槲覀円獙λM(jìn)行操作抵恋。比如我們可以使用 UIViewController 的方法,func performSegueWithIdentifier(identifier: String, sender: Anyobject?)
啟用 segue猛遍,但我們幾乎不用這種方法馋记,一般都在storyboard 中直接用 ctrl 拉線号坡。segue 的 id 更重要的一個(gè)用途是:preparing for a segue。用代碼來說就是:
func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { // sender 一般是 button梯醒,但可以是任何東西
if let identifier = segue.identifier { // 檢查是否 nil
switch identifier {
case "Show Graph":
if let vc = segue.destinationViewController as? GraphController { // 如果非 nil宽堆,則作為 GraphController
vc.property1 = ...
vc.CallMethodToSetItUp(...)
}
default: break
}
}
}
在上面的這段代碼中,有個(gè) switch case
語句茸习,而我們在 Demo 中畜隶,充分發(fā)揮了 Swift 的特性,使用了字典号胚,使得我們的代碼更加優(yōu)雅(這也是我為什么喜歡 Swift 的原因籽慢,相比于 CPP)。
private let emotionalFaces: Dictionary<String,FacialExpression> = [
注意:這個(gè)準(zhǔn)備(preparation)的過程是在設(shè)置好 outlet 之前完成的猫胁,其實(shí)也挺符合實(shí)際箱亿,因?yàn)橄劝言摐?zhǔn)備的東西準(zhǔn)備好了,才能去設(shè)置再去呈現(xiàn)弃秆。但在實(shí)際開發(fā)過程中很容易出現(xiàn) bug届惋,不自覺的就會(huì)去使用未設(shè)置好的 optional 變量。文末會(huì)介紹這樣的 bug 以及相應(yīng)的處理對策菠赚。
我們也可以阻止 segue脑豹,只要在 UIViewController 中實(shí)現(xiàn)下面這個(gè)方法,返回 false衡查。
func shouldPerformSegueWithIdentifier(identifier: String?, sender: AnyObject?) -> Bool
組合多個(gè) MVC
有3種方式可以將多個(gè) MVC 聯(lián)合起來瘩欺,
-
Tab Bar Controller:
每個(gè)界面應(yīng)該是相互獨(dú)立的,因?yàn)槿绻麅蓚€(gè)界面相互關(guān)聯(lián)拌牲,如上面提到的我們這次的 Demo俱饿,我們希望用戶點(diǎn)擊了某個(gè)相應(yīng)的事件按鈕才能看到下個(gè)界面,而不是直接點(diǎn)了和事件無關(guān)的 Tab Bar 就能看到下個(gè)界面们拙。所以我們的 Demo 不能用 Tab Bar Controller稍途。 -
Split Controller:
如我在 Swift 學(xué)習(xí)筆記3 - Gesture中有提到的阁吝, “對于一個(gè)split view來說砚婆,[0]是主體部分, [1]是細(xì)節(jié)部分”突勇。對于我們這次的 Demo装盯,帶有 “Angry”, "Worried" 等按鈕的界面可以是主體部分 (master),而那個(gè)呆萌的臉可以作為呈現(xiàn)細(xì)節(jié)的另一個(gè)view (detail)甲馋。 別忘記 Split Controller 是用于 ipad 的(以及6 plus)埂奈,那是因?yàn)?6 plus 以前的 iphone 屏幕不夠大,沒辦法 split定躏。要想在 6 里用 split账磺,我們還是需要借助下面的這個(gè)哥們芹敌,Navigation Controller. -
Navigation Controller:
選中主體部分(master),然后 Editor -> Embed in -> Navigation Controller垮抗。Boom氏捞!It worked! 其實(shí)它就是多了個(gè)Back
按鈕冒版,這也是 Navigation Controller 精髓所在液茎。在之前的學(xué)習(xí)筆記中也說過,Navigation Controller 是通過 stack 的方式存放這些 view 的辞嗡,Back
就代表將現(xiàn)在的這個(gè) view 拋棄掉(是徹底拋棄哦捆等,重新點(diǎn)擊按鈕,它創(chuàng)建的就是一個(gè)新的界面续室,和之前的那個(gè)毛線關(guān)系沒有啦)栋烤,然后返回上一個(gè) view。但如果是 Tab Bar挺狰,它就是一直存在的班缎,也就是說點(diǎn)擊一個(gè) tab A,再點(diǎn) B她渴,再點(diǎn) A达址,它出現(xiàn)的還是原來的 A。用教授的話說就是 “tab bar 不是 segue趁耗,navigation 是 segue”沉唠。
- 有一點(diǎn)需要注意的是,如果在一個(gè) Navigation Controller 中放有另一個(gè) Navigation Controller苛败,iOS 會(huì)自動(dòng)忽視里面的那個(gè) Navigation Controller满葛。
- 當(dāng)我們想在 detail 的那個(gè)界面加個(gè)標(biāo)題,我們還需要在那個(gè) detail 上 embed 一個(gè) Navigation Controller罢屈,但是這樣的話在 ipad 上運(yùn)行那些按鈕就沒用了嘀韧,因?yàn)?prepare 的是 UINavigationController, 而我們想要 prepare 的是 UINavigationController 里面的內(nèi)容,也就是我們之前做好的 detail view 里的東西缠捌。所以我們還需要加上這段代碼锄贷,也就是 prepare UINavigationController 里面的內(nèi)容的。
if let navcon = destinationvc as? UINavigationController {
destinationvc = navcon.visibleViewController ?? destinationvc
}
View Controller 生命周期
生命周期大概是這樣的:
- Creation. 一般在 storyboard 中創(chuàng)建實(shí)例曼月。
- Preparation if being segued to.
如之前所說谊却,這個(gè)準(zhǔn)備過程在 Outlet Setting 之前完成。 - Outlet Setting.
- Appearing and disappearing.
- Geometry changes.
- Low-memory situation. 一般在現(xiàn)在的 iphone 中很少發(fā)生哑芹。但要是發(fā)生了炎辨,可以釋放好久不用的占用很大空間的事件,比如最長未使用原則(LRU)聪姿。
在 storyboard 中創(chuàng)建實(shí)例碴萧,設(shè)置了 outlet 之后乙嘀,就是調(diào)用 viewDidLoad 方法了。viewDidLoad, viewWillLoad, viewWillAppear, viewWillDisappear, viewDidAppear, viewDidDisappear 等等這么多方法破喻,我會(huì)在下面逐條整理乒躺。
- viewDidLoad
view 只會(huì) load 一次,一般在這里在這里進(jìn)行一些初始化的工作低缩,我們一般不用 init 方法嘉冒,因?yàn)榇藭r(shí) outlet 已經(jīng)設(shè)置完畢了。我們一般也將 update UI 的工作放在這個(gè)方法里咆繁。但是 view 的 geometry 不在這里設(shè)置讳推,因?yàn)檫€不知道使用的設(shè)備是什么。這里的 geometry 的意思是 view 的大小尺寸玩般,橫向還是縱向之類的银觅。
override func viewDidLoad() {
super.viewDidLoad()
// 進(jìn)行一些 MVC 初始化的工作
}
- viewWillAppear
這是 view 即將呈現(xiàn)出來之前的方法,一般把需要大量運(yùn)算的程序放在這里(比如多線程的操作)坏为,view 的 geometry 也是在這里設(shè)置的究驴,但是如果要做旋轉(zhuǎn)之類的操作,其它地方會(huì)響應(yīng)這些操作匀伏。
override func viewDidLoad() {
super.viewDidLoad()
// 進(jìn)行一些 MVC 初始化的工作
}
- viewDidAppear
這是在 view 呈現(xiàn)出來之后的方法洒忧,一些動(dòng)畫在這里進(jìn)行。
func viewDidAppear(animated: Bool)
- viewWillDisappear
這是 view 即將消失之前的方法够颠,主要做一些簡單的清理工作熙侍,但一些非常耗時(shí)的工作不是在這里進(jìn)行的,這里可以關(guān)閉動(dòng)畫履磨。
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated) // 在所有的 viewWill/Did 方法中蛉抓,調(diào)用 super。
// 進(jìn)行一些 MVC 初始化的工作
}
- viewDidDisappear
這是在 view 消失之后的方法剃诅,釋放一些在 willappear 階段從網(wǎng)絡(luò)上拿來的數(shù)據(jù)巷送。
func viewDidDisappear(animated: Bool)
-
func viewWillLayoutSubviews()
和func viewDidLayoutSubviews()
,這兩個(gè)方法用于幾何變換的時(shí)候(geometry)矛辕,而我們在 storyboard 中相對應(yīng)于那些藍(lán)線設(shè)置的 constraints笑跛,是在這兩個(gè)方法之間發(fā)生的。這里其實(shí)我們不需要做什么特殊操作如筛,因?yàn)槎际亲詣?dòng)完成的堡牡。 - viewWillTransitionToSize
在做旋轉(zhuǎn)變換的時(shí)候抒抬,就是將手機(jī)或者 pad 屏幕橫過來的時(shí)候杨刨,我們可以在這里設(shè)置一些動(dòng)畫屬性。
func viewWillTransitionToSize() {
size: CGSize,
withTransitionCoordinator: UIViewControllerTransitionCoordinator
}
- awakeFromNib
在 preparation 和 outlet set 之前(在 MVC load 之前)擦剑。這個(gè)會(huì)發(fā)給任何對象妖胀,不只是 ViewController芥颈。
概括這個(gè)周期,就是:
- Instantiated (一般從 storyboard 中創(chuàng)建實(shí)例)
- awakeFromNib
- segue preparation
- outlet set
- viewDidLoad
以下這些會(huì)經(jīng)常調(diào)用赚抡,比如每次顯示和關(guān)閉某個(gè) view 的時(shí)候 - viewWillAppear & viewDidAppear
- viewWillDisappear & viewDidDisappear
以下幾何變換的方法有可能在 viewDidLoad 之后的任何時(shí)候被調(diào)用 - viewWillLayoutSubviews
- 自動(dòng)部署布局
- viewDidLayoutSubviews
- didReceiveMemoryWarning (內(nèi)存不夠的時(shí)候)
Demo 中的 bug 和對策
有個(gè) bug 在 iOS 開發(fā)中應(yīng)該會(huì)經(jīng)常遇到爬坑,就是
fatal error: unexpectedly found nil while unwrapping an Optional value
如 Paul Hegarty 教授所講,
Your outlets are not set at the time you preparing
這是因?yàn)橛袀€(gè) optional 我們沒有處理涂臣,比如在這個(gè) demo 中盾计,faceView 是 optional 的,faceView 是這樣來的:
@IBOutlet weak var faceView: FaceView!
所以在后面 update faceView 的時(shí)候赁遗,需要考慮它 nil 的情況署辉,有兩種措施:
- 第一種方法,以其中一條語句為例
faceView?.eyeBrowTilt = eyeBrowTilts[expression.eyeBrowns] ?? 0.0
注意等號左邊的問號 faceView?
岩四,它可以處理 optional nil 的問題哭尝,如果式子的任何一個(gè)地方為 nil,那么它就會(huì) ignore 整條語句剖煌。但是這樣的語句很多材鹦,我們不能每條都這樣處理。僅管我們是碼農(nóng)程序猿耕姊,我們也要學(xué)會(huì)優(yōu)雅桶唐。:)
- 另一個(gè)方法就是加上
if faceView != nil { }
,這樣就避免了 nil 問題了茉兰。