iOS狂暴之路---視圖控制器(UIViewController)使用詳解

一、前言
在之前的一片文章中已經介紹了 從iOS的第一個應用中能學習到哪些知識點 在那篇文章中主要介紹了一個iOS程序的啟動過程和應用的幾大對象,以及應用的生命周期,同時也介紹了應用中的控制器知識點,介紹了其生命周期方法,那么對于一個iOS應用一般都是會包含多個頁面,而每個頁面就是一個控制器,一個控制器一般都是關系到一個UIView的,但是我們在真正使用這些控制器的時候會發現,多個頁面之間的跳轉關系該如何控制。在之前的文章知道一個應用對應一個窗口對象UIWindow,每個窗口都有一個根控制器對象,那么如果一個應用有多個控制器該如何管理這些控制器呢?那么就是本文需要介紹的重點了。

二、兩個視圖控制器
Android中我們知道每個頁面都是一個Activity,每個頁面之間的跳轉以及通信都是采用Intent對象進行傳遞的,那么在iOS中并沒有這種機制了,而在iOS中管理多個控制器一般都是兩種控制器:
一種是切換控制器UITabBarController,一種是導航控制器UINavigationController
這兩種控制器雖然是管理多個控制器,但是他們兩本身也是個控制器類,而且他們兩個都有各自的使用場景。
1、UITabBarController一般用于首頁中的頁面切換,比如微信的首頁中四個Tab切換就是采用這個控制器管理的:


這個有點類似于Android中的ViewPager+Fragment實現的功能。

2、UINavigationController一般用于從一個控制器頁面跳轉到另外一個頁面控制器,這個就和Android中的Activity跳轉非常相似了。而且也是使用場景最多的一個了。

三、切換控制器UITabBarController
下面先來介紹第一個控制器管理類:UITabBarController類,首先不多說,還是老樣子,先建立一個簡單的案例:



選擇需要繼承的父類UITabBarController,其實他也是一個控制器:



那下面我們需要把這個控制器設置成根控制器,然后在通過這個控制器來管理后續添加的子控制器內容:

在AppDelegate回調方法中和之前一樣的方式設置根控制器,下面就來開始添加子控制器了:

因為需要多個子控制器進行操作案例,所以這里就新建了兩個控制器類,然后初始化之后記得設置子控制器的tabBarItem屬性,這個屬性代表著這個子控制器在UITabBarController的item樣式屬性。當然這里為了簡單就采用系統提供的一些樣式了,也可以自定義自己的樣式的,這個后面等介紹具體項目的時候再說,其實不難也是一些屬性的使用罷了。初始化完成之后就把所有的子控制器添加到一個NSArray中,最后在設置到UITabBarController中即可。下面來看一下運行效果:



看到底部有兩個可以切換的item,看到他們的樣式就是系統對應的聯系人和更多的樣式,當然我們可以自定義這樣的樣式,可以設置item的圖片和文字。這里就不演示了。

到這里我們會發現這個控制器真的和Android中的ViewPager+Fragment非常相似,那么問題來了,我們在Android中使用ViewPager+Fragment進行開發的時候,僅僅的簡單添加子控制器是滿足不了需求的,我們一般還需要知道一些事,這里主要是兩件事:
第一件事:每個子控制器之間的切換事件,也就是tab切換的回調事件
第二件事:在切換的過程中每個子控制器的生命周期會發什么變化
那么下面就在來詳細分析一下這兩件事,先來看第一件事:如何監聽每個子控制器之間的切換事件
這個和Android中也非常類似的,就是給切換控制器添加代理方法,當然在Android中叫做回調方法。這里添加代理非常簡單:


因為我們在AppDelegate類中定義了控制器,所以就需要AppDelegate類實現代理協議了:UITabBarControllerDelegate,實現之后我們就可以實現幾個代理方法了:

第一個方法:這個代理方法是在子控制器切換完成之后調用,參數傳回來的是當前選中的子控制器。

  • (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController

第二個方法:這個代理方法是決定當前子控制器是否可以被選擇,如果返回YES表示可以選中,如果返回NO表示不可選中,也就是不可切換操作了。

  • (BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController

當然還有其他代理方法,這里就不一一詳細介紹了,因為這兩個方法在實際開發中用到的最多。這里順便再來講一下這個控制器的兩個常用的屬性:
第一個屬性:selectedIndex代表的是獲取到當前選中的子控制器的索引值,這個屬性可以讀寫操作,也就是可以設置索引值來決定當前哪個控制器被選中,一般是在初始化的時候會決定當前哪個控制器被默認選中。
第二個屬性:selectedViewController代表當前選中的子控制器,這個屬性也是可以讀寫操作的,其實他的功能和上面的索引值功能差不多,只是一個是操作子控制器索引值,一個是直接操作子控制器對象的。
有了這兩個屬性在結合上面的那兩個代理方法就可以滿足我們的開發需求了。

下面在來解決第二件事,就是各個子控制器在切換過程中他們的生命周期會發什么變化,其實我們在前面一篇文章中已經介紹了控制器的生命周期方法了:
1> view初始化完畢后,就會調用控制器的viewDidLoad方法2> view初始化完畢后,就會把這個根控制器的view添加到窗口中3> 當view即將被添加到窗口中時,就會調用控制器的viewWillAppear:方法4> 當view已經被添加到窗口中時,就會調用控制器的viewDidAppear:方法5> 如果控制器的view即將從窗口中移除時,就會調用控制器的viewWillDisappear:方法6> 如果控制器的view已經從窗口中移除時,就會調用控制器的viewDidDisappear:方法7> 如果控制器接收到內存警告的時候,就會調用控制器的didReceiveMemoryWarning方法didReceiveMemoryWarning方法的默認實現是:如果控制器的view沒有顯示在窗口中,也就是說controller.view.superview為nil時,系統就會銷毀控制器的view.8> 銷毀完畢后會調用控制器的viewDidUnload方法9> 如果控制器的view以前因為內存警告被銷毀過,現在需要再次訪問控制器的view時,會重復前面的步驟初始化view
這里我們主要是來看一下viewWillAppear,viewDidAppear,viewWillDisapper,viewDidDisappear方法,其他方法這里可能用不到了。我們為了方便查看效果,可以定義一個BaseViewController類,然后在其生命周期方法中添加日志信息,最后讓子控制器都繼承這個類,這樣每個子控制器的生命周期方法都可以發現了:


這里看到我們是如何添加日志的信息的,首先需要知道是哪個子控制器所以需要打印子控制器名稱,可以使用self關鍵字,然后就是方法名稱了,這里因為不想手動的去寫方法名,所以就用NSStringFromSelector(_cmd)來獲取當前方法的名稱。
下面我們再次運行程序,然后多次切換子控制器看看效果:

從日志中我們可以得到三個重要信息:
第一個信息:當一個子控制器只有當要被顯示的時候才會調用viewDidLoad代理方法,看到開始的時候,第二個子控制器的這個方法并沒有調動,當我切換到第二個控制器的時候進行展示才調用了,可以理解為懶加載機制。用到才進行加載view。
第二個信息:每個子控制器的視圖加載代理方法vieDidLoad只會調用一次,也就是第一次展示的時候,后面再次展示就不會再次調用了,當然這個不是絕對的,比如如果這時候系統內存不足,會回收一些資源,那么這時候可能會把子控制器進行回收,那么下次再次切換到這個子控制器的時候還是會調用他的加載方法,但是大部分情況下都只調用一次。
第三個信息:也就是我們最關心的信息,就是每個子控制器在來回切換的過程中會回調viewDidAppear,viewDidDisAppear等方法。所以如果我們需要做一些操作就要在這兩個代理方法中進行了。

到這里我們就算介紹完了iOS中的第一個控制器管理類UITabBarController,其實他和Android中的ViewPager+Fragment非常類似,我們從在Android中的使用需求可以在iOS中得到我們在實際開發使用中想要得到的一些信息,這里一般就是兩件事:第一件事就是切換的回調也就是代理方法,第二件事就是在切換過程中各個子控制器的生命周期發生了如何變化。最后就是幾個重要的屬性,比如如何得到當前切換到哪個子控制器了,如何手動的設置到默認的選擇到哪個子控制器上等。有了這些信息我們就可以滿足正常的程序開發了。那么接下來我們還要來介紹一個控制器管理類,這個類在實際開發中用的就比較多了。

四、導航控制器UINavigationController
導航控制器UINavigationController類在實際開發過程中用到的可能比較多,一般從一個頁面跳轉到另外一個頁面就需要用這個控制器了,我們還是和上面的步驟一樣,開始的時候簡單的新建一個這個控制器,記得需要繼承UINavigationController類:


他其實也是一個控制器:

定義好之后,咋們就可以在AppDelegate回調方法中設置應用窗口控制器的根控制器類:

這里和UITabBarController有個區別,這個控制器其實和Android的Activity非常類似,因為這里也是采用棧的結構,在Android中所有的Activity有一個棧結構維護的。但是這里比Android簡單,就是沒有那么多復雜的啟動模式啥的,只要記得是用棧結構來維護應用中的控制器即可。那么關于棧的操作就是出棧和入棧,而這里的棧頂的控制器是展示在當前應用中的,所以如果我們想進入到某個頁面,那么只需要把這個控制器頁面入棧即可,如果要返回就直接出棧即可。
下面咋們還是用上面那兩個子控制器作為案例進行操作,開始的時候咋們把第一個子控制器進行入棧進行展示:

看到頂部有兩個選項,其實這個是導航控制器對于每個子控制器的一個導航item的標題設置,這個item一般包括標題,左邊item,右邊item,而這些item就和之前的tabitem類似,有icon和文字。這里我們把右邊的item添加點擊事件,點擊之后就跳轉到了第二個控制器,在第二個控制器的導航item中添加左邊item點擊事件,點擊就返回:

在第一個子控制器中,我們設置了導航item的標題內容,左右item內容,這里依舊采用了系統的樣式,然后添加了點擊事件,而在點擊之后跳轉到第二個子控制器也是直接采用入棧操作即可,這里需要注意的是,對于每個子控制器都可以使用navigationController屬性來獲取他的導航控制器對象,然后就可以操作棧了。從這里也可以看到,在第一個控制器中肯定用到了第二個控制器所以需要導入類定義,同時我們一般在跳轉的時候需要攜帶數據的,那么這里就可以直接通過第二個控制器對象的一些方法設置即可。這個和Android中不一樣了,可以把數據捆綁到Bundle對象打包發過去了。

在第二個子控制器中,我們沒有定義導航item了,但是我們還是看到了,這個是系統默認就有的效果,當然我們也可以不要,但是一般都會保留的。當然我們自己也模擬了點擊返回的效果:

我們這里可以看到導航控制器提供了三個方式出棧的方法,下面來看一下他們三個的區別:
1、popViewControllerAnimated:
這個方法是我們用的最多的,就是直接出棧操作,相當于刪除棧頂對象,那么就有了返回的效果了。
2、popToRootViewControllerAnimated:
這個方法看名稱可以知道也是出棧,但是他出的非常徹底,直接回到了棧底,把棧里的子控制器都出棧了。
3、popToViewController:animated:
這個方法看多了個參數,也就說可以出棧到指定的控制器那個位置,就是指定的控制器之前的子控制器都得出棧。
其實看到這三種方式和Android中的Activity的啟動模式非常相似。
第一個方法對應的是Android中的singleTop啟動模式
第二個方法對應的是Android中的singleTask啟動模式
第三個方法對應的是Android中的singleInstance啟動模式
當然這里只是為了好理解,就和Android中作比較,可以發現也并不是完全一致的。不過這三種出棧方式也是非常好理解的,因為他們三個方法正好能夠滿足我們開發中所有的出棧的需求了。

再看一下后面的一段代碼,執行了一個代理對象方法,其實這個就是做了當前子控制器返回之后需要攜帶一些返回數據給上一個子控制器的功能,那為什么這里不在這個子控制器類中導入第一個子控制器定義,然后直接調用其方法得到返回數據呢?其實想想應該不能這么做,因為我們知道從一個控制器跳轉到下一個控制器只有一條路走。所以導入是沒有關系的,但是如果從一個控制器返回到上一個控制器就有多條路了,因為這個控制器可能由多個控制器跳轉過來的,那么如果都需要返回值,就需要導入每個跳轉過來的控制器定義了,可想而知這個子控制器類會變得非常龐大和耦合。所以這里我們可以在第二個控制器中定義一個協議,所有需要跳轉過來的控制器都可以實現這個協議,然后在第二個控制器中就可以非常的靈活調用這個id類型對象的指定方法,實現返回值功能了。


在返回的時候,首先判斷一下代理對象有沒有對應的代理方法,有的話就開始調用即可。

和之前的切換控制器一樣,我們在使用導航控制器也是需要解決兩件事:
第一件事:子控制器在入棧和出棧的代理方法
第二件事:子控制器在入棧和出棧的時候自身的生命周期變化
下面來看一下第一件事,也是和之前一樣,這里我們也是需要實現一個協議:UINavigationControllerDelegate,還是AppDelegate類需要實現的。
第一個方法:這個代理方法的功能是即將要展示哪個控制器了,也就是入棧操作了。

  • (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated
    第二個方法:這個代理方法的功能是哪個控制器進行展示了,也就是棧頂控制器。

  • (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated
    所以從這兩個方法中可以看到,這些代理方法其實就是監聽棧頂變化的,如果棧頂控制器發生變化之后就會回調方法。

下面繼續來看第二件事,因為我們沿用了上面的子控制器,所以日志都加好了,咋們還是直接來回切換幾次看看效果:



從生命周期上可以看到,和上面的切換控制器一樣,各個子控制器都是采用懶加載機制,用到展示的采取進行加載,以后只會調用viewDidDisappear和viewDidAppear等方法了。這里從日志也可以看到我們可以正確的獲取到從上個子控制器中傳過來的數據,也可以獲取到前一個子控制器返回的數據。

五、視圖控制器總結
到這里我們就介紹完了iOS中常用的兩個控制器管理類,也就是一個應用中多個控制器之間的跳轉切換等效果。下面來總結一下:
第一個:切換控制器UITabBarController
這個控制器一般用于首頁的切換tab功能,比如微信的那樣的效果,他和Android中的ViewPager+Fragment組合使用效果非常類似,在使用的過程中,需要使用一個子控制器數組存放所有的子控制器。然后每個子控制器之間的切換操作有對應的回調代理方法,
1、監聽到切換到哪個子控制器
2、可以指定返回值來設置哪個子控制器不可切換選擇
在這個過程中每個子控制器的生命周期方法是:
1、所有子控制器都是采用懶加載機制,需要展示的時候才去加載
2、如果已經加載過得子控制器下次再次切換的時候只會調用viewDidAppear和viewDidDisapper等方法
最后就是有兩個重要屬性:
1、第一個屬性是當前選擇的子控制器的索引值,這個值可以讀寫,通過這個值可以設置默認選擇哪個子控制器
2、第二個屬性是當前選擇的子控制器對象,這個值可以讀寫
對于每個子控制器都有一個屬性:tabBarController 可以獲取到當前的切換控制器對象。


第二個:導航控制器UINavigationController
這個控制器用的比較多,一般用于程序的多個子控制器之間跳轉,這個控制器有一個特殊的地方就是他采用的是棧結構來管理子控制器,這一點和Android中的Activity非常類似,也是采用棧結構。那么對于跳轉就是入棧操作,返回就是出棧操作。操作也是非常簡單的。同樣的這里我們在操作的時候也是有兩個件事需要知道,一個是各個子控制器在跳轉的時候的回調代理方法:
1、監聽當前棧頂變化的回調代理方法
還有一個就是需要知道每個子控制器的生命周期方法變化:

1、所有子控制器都是采用懶加載機制,需要展示的時候才去加載。
2、如果已經加載過得子控制器下次再次切換的時候只會調用viewDidAppear和viewDidDisapper等方法
然后需要注意的是在出棧的時候有三種方式:

1>、popViewControllerAnimated:
這個方法是我們用的最多的,就是直接出棧操作,相當于刪除棧頂對象,那么就有了返回的效果了。
2>、popToRootViewControllerAnimated:
這個方法看名稱可以知道也是出棧,但是他出的非常徹底,直接回到了棧底,把棧里的子控制器都出棧了。
3>、popToViewController:animated:
這個方法看多了個參數,也就說可以出棧到指定的控制器那個位置,就是指定的控制器之前的子控制器都得出棧。
而這三種方式和Android中的啟動模式都有對應的方式,有了這三種方式就可以滿足我們日常中開發需要了。
最后是每個子控制器都可以通過navigationController屬性獲取到導航控制器對象

項目下載地址:http://download.csdn.net/detail/jiangwei0910410003/9667736

七、總結
本文就介紹完了iOS中的控制器管理類,主要是用來解決一個應用中多個子控制器之間的切換和跳轉問題,在iOS中的操作非常簡單,不想Android中那么復雜。有了這個知識之后后面我們就可以簡單的開發一個應用了,這個應用可以包含多個控制器頁面了,然后需要做的是每個控制器展示的內容,也就是對應的具體UIView了。這個將是我們后面文章需要詳細介紹的內容了。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,606評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,582評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,540評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,028評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,801評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,223評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,294評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,442評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,976評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,800評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,996評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,543評論 5 360
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,233評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,662評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,926評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,702評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,991評論 2 374

推薦閱讀更多精彩內容