1. 程式人生 > >iOS開發中ViewController使用詳解

iOS開發中ViewController使用詳解

一、前言

在之前的一片文章中已經介紹了 從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屬性獲取到導航控制器物件

七、總結

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