Swift Talk #05 Connecting View Controllers

我們把app的流從Storyboard中重構(gòu)代碼到單獨的協(xié)調(diào)類中。這樣就避免了View Controller緊耦與他們的上下文豺旬。

今天我們來談?wù)劰适掳婧腿绾胃倪M故事版的使用钠惩。我們有一個app來顯示Table View.如果你點擊table里面的一項,會進入詳情頁哈垢。也有一個profile按鈕妻柒,用來彈出一個模態(tài)navigation Controller包裹的另一個view Controller扛拨。如果你點擊了完成耘分,模態(tài)navigation Controller會消失。

s01e01-storyboard.png

在故事版中绑警,我們有一些View Controller: navigation Controller; 視頻集的列表求泰;詳情View Controller,最終navigation Controller包括了模態(tài)個人中心視圖计盒。在代碼里渴频,我們有3個類:ProfileViewController和DetailViewController.EpisodesViewController有一點復(fù)雜。是一個簡單的table View Controller北启,但是又有我們想重構(gòu)的prepareForSegue方法卜朗。prepareForSegue方法區(qū)分兩個不同的segue和配置各自的view Controller。最終未解開的IBAction擁有segue無論何時模態(tài)View Controller消失咕村。

故事版給我們可視化的展示了view Controllers是如何鏈接的场钉。然而想要改變view Controller的鏈接就不靈活,應(yīng)為并不是所有的東西都在故事版中配置懈涛,比如segues在故事版中逛万,而prepareForSegue方法又是在View Controller中。我們需要當心代碼和故事版的配合批钠。比起把view Controller的鏈接放在兩者都有宇植,我們當然是只放在一個地方會好些。

重構(gòu)故事版

首先埋心,我們把故事版里面的segues刪掉指郁。為了布局這些視圖,我們先保留故事版拷呆。我們刪掉push segue闲坎,模態(tài)展示segue,并解開IBAction。現(xiàn)在我們的故事版僅僅被用來定義view Controllers箫柳。

在我們的代碼中手形,我們先刪除prepareForSegue和解開IBAction。現(xiàn)在我們必須找個不同的方式來鏈接我們的view Controllers悯恍。首先我們重寫tableView:didSelectRowAtIndexPath這個方法库糠。在這個方法中,我們不想push到下一個View Controller涮毫,因為這樣就把兩個view Controller纏住了瞬欧。相反我們想從外面控制流。這樣的話view Controllers是獨立的罢防,而且并不知道他們的被使用的上下文艘虎。為了實現(xiàn)這個,當一個row被選中的時候咒吐,我們就調(diào)用一個方法didSelect傳入被選中的episode野建。

override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
    let episode = episodes[indexPath.row]
    didSelect(episode)
}

didSelect的屬性很簡單,是一個方法類型Episode -> (),并且我們提供一個空的默認實現(xiàn)恬叹。

var didSelect: (Episode) -> () = { _ in }

現(xiàn)在我們在AppDelegate中配置這個屬性候生。首先,我們通過window對象獲得navigation Controller的引用绽昼。其次我們再navigation controller棧中的第一個view Controller獲取EpisodesViewController的引用唯鸭。

let nc = window?.rootViewController as! UINavigationController
let episodesVC = nc.viewControllers[0] as! EpisodesViewController

強制轉(zhuǎn)換并不好,但是我們堅持用故事版就沒辦法硅确。我可以可以去除強制轉(zhuǎn)換如果我們用純代碼的方式來構(gòu)建目溉,但是我們使用故事版就必須使用強制轉(zhuǎn)換。然而我們可以創(chuàng)建一個中心地來做這個菱农,舉個栗子缭付,在UIStoryboard的擴展中。

Connecting Two View Controllers

既然我們引用了EpisodesViewController,我們能設(shè)置didSet屬性大莫。匿名方法中傳入episode并且在navigation Controller中調(diào)用pushViewController蛉腌。因我們想在故事版中實例化我們的詳情View Controller,我們先創(chuàng)建故事版的引用只厘。我們?nèi)缓罂梢酝ㄟ^調(diào)用instantiateViewControllerWithIdentifier來得到詳情View Controller.

let storyboard = UIStoryboard(name: "Main", bundle: nil)
episodesVC.didSelect = { episode in
    let detailVC = storyboard.instantiateViewControllerWithIdentifier("Detail") as! DetailViewController
    nc.pushViewController(detailVC, animated: true)
}

我們已經(jīng)連接了這兩個view Controllers, 并且我們的episodes View Controller并不知道詳情View Controller烙丛,因為流僅僅在didSelect的回調(diào)用被控制了。最終我們需要配置詳情View Controller羔味,所以我們傳入我們在didSelect回調(diào)中的到的episode河咽。

episodesVC.didSelect = { episode in
    let detailVC = storyboard.instantiateViewControllerWithIdentifier("Detail") as! DetailViewController
    detailVC.episode = episode
    nc.pushViewController(detailVC, animated: true)
}

在我們的app中,我們可以選擇cell并且詳情View Controller被正確的配置赋元。然而忘蟹,個人中心頁面還沒有弄好飒房。在我們的故事板中,我們需要連接個人中心按鈕和episodes view controller中的一個action媚值。

class EpisodesViewController: UITableViewController {
    // ...
    @IBAction func showProfile(sender: AnyObject) {
        // TODO
    }
}

我們也需要稍后釋放個人中心頁面狠毯,所以我們也需要為它創(chuàng)建一個action。

class ProfileViewController: UIViewController {
    // ...
    @IBAction func close(sender: AnyObject) {
        // TODO
    }
}

在showProfile action中褥芒,我們不想寫死ProfileViewController的展示嚼松。就像剛剛一樣,我們在EpisodesViewController中創(chuàng)建一個方法屬性的didTapProfile锰扶。

class EpisodesViewController: UITableViewController {
    // ...
    var didTapProfile: () -> () = {}

    @IBAction func showProfile(sender: AnyObject) {
        didTapProfile()
    }
}

我們給ProfileViewController中的關(guān)閉action做同樣的事献酗。

class ProfileViewController: UIViewController {
    // ...
    var didTapClose: () -> () = {}

    @IBAction func close(sender: AnyObject) {
        didTapClose()
    }
}

在AppDelegate中,我們用閉包來配置didTapProfile屬性坷牛,閉包中我們實例化ProfileViewController并且顯示罕偎。

episodesVC.didTapProfile = {
    let profileVC = storyboard.instantiateViewControllerWithIdentifier("Profile") as! UINavigationController
    nc.presentViewController(profileVC, animated: true, completion: nil)
}

為了讓消失一樣起作用,我們也這樣配置didTapClose屬性京闰。在這個閉包中我們需要在navigation Controller中彈出ProfileViewController颜及。

episodesVC.didTapProfile = {
    let profileNC = storyboard.instantiateViewControllerWithIdentifier("Profile") as! UINavigationController
    let profileVC = profileNC.viewControllers[0] as! ProfileViewController
    profileVC.didTapClose = {
        nc.dismissViewControllerAnimated(true, completion: nil)
    }
    nc.presentViewController(profileNC, animated: true, completion: nil)
}

這個模式解耦了我們的view Controllers,他們相互不知道忙干,也不相互顯示器予。他們在外面的一個集中的地方被連接起來浪藻,因此這些View Controllers并不知道他們被包含在一個navigation Controller中捐迫。然而把所有的這些代碼放到AppDelegate中也不好。我們需要繼續(xù)重構(gòu)爱葵。

創(chuàng)建一個App類

最簡單的改進現(xiàn)狀的方式就是把所有的代碼放到我們自己的App類里面施戴。

class App {
    init(window: UIWindow) {
        let nc = window.rootViewController as! UINavigationController
        let episodesVC = nc.viewControllers[0] as! EpisodesViewController
        let storyboard = UIStoryboard(name: "Main", bundle: nil)
        episodesVC.didSelect = { episode in
            let detailVC = storyboard.instantiateViewControllerWithIdentifier("Detail") as! DetailViewController
            detailVC.episode = episode
            nc.pushViewController(detailVC, animated: true)
        }
        episodesVC.didTapProfile = {
            let profileNC = storyboard.instantiateViewControllerWithIdentifier("Profile") as! UINavigationController
            let profileVC = profileNC.viewControllers[0] as! ProfileViewController
            profileVC.didTapClose = {
                nc.dismissViewControllerAnimated(true, completion: nil)
            }
            nc.presentViewController(profileNC, animated: true, completion: nil)
        }
    }
}

在AppDelegate中,我們給App創(chuàng)建一個屬性萌丈,并實例化這個屬性赞哗。

class AppDelegate: UIResponder, UIApplicationDelegate {
    var window: UIWindow?
    var app: App?

    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
        if let window = window {
            app = App(window: window)
        }
        return true
    }
}

我們的問題在于我們的App.init是一堆代碼。里面有很多回調(diào)辆雾,包括了回調(diào)中的回調(diào)肪笋。我們應(yīng)該通過把這些回調(diào)拿出來來改進我們的代碼,因為回調(diào)的情況只會把這些變得復(fù)雜:navigation棧中的每一層會引起另一個嵌套的回調(diào)度迂。

刪除嵌套回調(diào)

放棄用閉包配置回調(diào)藤乙,我們可以把代碼放到didSelectEpisode方法中。

func didSelectEpisode(episode: Episode) {
    let detailVC = storyboard.instantiateViewControllerWithIdentifier("Detail") as! DetailViewController
    detailVC.episode = episode
    navigationController.pushViewController(detailVC, animated: true)
}

在我們的閉包中惭墓,我們可以調(diào)用didSelectEpisode方法坛梁。

episodesVC.didSelect = { episode in
    self.didSelectEpisode(episode)
}

didSelectEpisode聽起來像是一個回調(diào)方法。改進這個命名腊凶,用其他的比如showEpisode可能會更好划咐。

func showEpisode(episode: Episode) {
    let detailVC = storyboard.instantiateViewControllerWithIdentifier("Detail") as! DetailViewController
    detailVC.episode = episode
    navigationController.pushViewController(detailVC, animated: true)
}

為了編譯這些拴念,我們把故事版和navigation Controller拉出來放到App類中。

final class App {
    let storyboard = UIStoryboard(name: "Main", bundle: nil)
    let navigationController: UINavigationController

    init(window: UIWindow) {
        navigationController = window.rootViewController as! UINavigationController
        // ...
    }
    // ...
}

為了不寫閉包和調(diào)用這個方法褐缠,我們可以配置didSelect方法政鼠,這樣寫清爽多了。

episodesVC.didSelect = showEpisode

我們可以給個人中心的選擇做同樣的事情队魏。我們創(chuàng)建showProfile方法并從閉包中拉出來缔俄。

init(window: UIWindow) {
    navigationController = window.rootViewController as! UINavigationController
    let episodesVC = navigationController.viewControllers[0] as! EpisodesViewController
    episodesVC.didSelect = showEpisode
    episodesVC.didTapProfile = showProfile
}

func showProfile() {
    let profileNC = self.storyboard.instantiateViewControllerWithIdentifier("Profile") as! UINavigationController
    let profileVC = profileNC.viewControllers[0] as! ProfileViewController
    profileVC.didTapClose = {
        self.navigationController.dismissViewControllerAnimated(true, completion: nil)
    }
    navigationController.presentViewController(profileNC, animated: true, completion: nil)
}

有了這些方法命名確實增加了可讀性,因為這些命名方法比所有這些嵌套的回調(diào)都容易理解器躏。

我們成功的讓view Controllers簡單俐载。僅僅app類知道他們是怎么連接的。為了展示這個改變有多簡單登失,我們可以在一個視頻集被點擊的時候遏佣,展示個人中心view controller。我們只需要在一個地方改變揽浙,就能讓我們的app不同状婶。

episodesVC.didSelect = { _ in self.showProfile() }

App這個類代碼密度比較大,但是其他的代碼都很簡單而且解耦馅巷。還有個問題膛虫,當你看到一個持有self引用的閉包的時候,你需要知道是否形成了循環(huán)引用钓猬。在我們的實例中稍刀,沒有。舉個栗子敞曹,只有navigation Controller引用了profile View Controller账月。一旦你點擊了返回,引用頁釋放了澳迫。不過所有代碼這些閉包可能會引起你的思考局齿,是不是需要使用weak?當重構(gòu)的時候橄登,還是容易一不小心就造成循環(huán)引用了抓歼。

我們的方法可以方便的創(chuàng)建更多可服用的view Controller.舉個栗子,我們可以使用一些泛型View Controller在不同的地方復(fù)用拢锹。讓我們期待后面的幾個視頻集吧。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末面褐,一起剝皮案震驚了整個濱河市拌禾,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌展哭,老刑警劉巖湃窍,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件闻蛀,死亡現(xiàn)場離奇詭異,居然都是意外死亡您市,警方通過查閱死者的電腦和手機觉痛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來茵休,“玉大人薪棒,你說我怎么就攤上這事¢泡海” “怎么了俐芯?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長钉鸯。 經(jīng)常有香客問我吧史,道長,這世上最難降的妖魔是什么唠雕? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任贸营,我火速辦了婚禮,結(jié)果婚禮上岩睁,老公的妹妹穿的比我還像新娘钞脂。我一直安慰自己,他們只是感情好捕儒,可當我...
    茶點故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布冰啃。 她就那樣靜靜地躺著,像睡著了一般肋层。 火紅的嫁衣襯著肌膚如雪亿笤。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天栋猖,我揣著相機與錄音,去河邊找鬼汪榔。 笑死蒲拉,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的痴腌。 我是一名探鬼主播雌团,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼士聪!你這毒婦竟也來了锦援?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤剥悟,失蹤者是張志新(化名)和其女友劉穎灵寺,沒想到半個月后曼库,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡略板,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年毁枯,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片叮称。...
    茶點故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡种玛,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出瓤檐,到底是詐尸還是另有隱情赂韵,我是刑警寧澤,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布挠蛉,位于F島的核電站右锨,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏碌秸。R本人自食惡果不足惜绍移,卻給世界環(huán)境...
    茶點故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望讥电。 院中可真熱鬧蹂窖,春花似錦、人聲如沸恩敌。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽纠炮。三九已至月趟,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間恢口,已是汗流浹背孝宗。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留耕肩,地道東北人因妇。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像猿诸,于是被迫代替她去往敵國和親婚被。 傳聞我的和親對象是個殘疾皇子永罚,可洞房花燭夜當晚...
    茶點故事閱讀 42,722評論 2 345

推薦閱讀更多精彩內(nèi)容

  • 介紹 objc.io objc.io 是關(guān)于 Objective-C 最佳實踐和先進技術(shù)的期刊休雌,歡迎來到第一期! ...
    評評分分閱讀 1,685評論 5 24
  • /* UIViewController is a generic controller base class th...
    DanDanC閱讀 1,793評論 0 2
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫诚纸、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,022評論 4 62
  • 答: 1)px 像素(Pixel)逗余。絕對單位。像素 px 是相對于顯示器屏幕分辨率而言的季惩,是一個虛擬長度單位录粱,是...
    jqClub閱讀 605評論 0 0
  • 堅持每天喝一碗小米粥有一陣了啥繁,胃好了很多。現(xiàn)在已經(jīng)習(xí)慣性在早晨醒來去煮小米粥青抛,因為邊煮粥還可以邊聽書旗闽。 這樣,我可...
    譽軒花匠閱讀 186評論 0 0