iOS 實(shí)現(xiàn)一個(gè)容器視圖控制器

一直以來想寫一個(gè)抽屜效果忍饰,看了一些文章后發(fā)現(xiàn)并不是那么簡(jiǎn)單,網(wǎng)上的一些抽屜效果不是很嚴(yán)謹(jǐn)寺庄“叮看了下MMDrawerController的源碼,等于定制了一個(gè)Container View Controller斗塘。(類似于系統(tǒng)的UINavigationController以及UITabbarController);

比如下面幾個(gè)方法就是MMDrawerController實(shí)現(xiàn)的:

WX20171225-105701.png

下面的描述是官方文檔幫助理解什么是容器控制器赢织,文檔中以UINavigationControllerUISplitViewController舉例。文檔

容器視圖控制器是將來自多個(gè)視圖控制器的內(nèi)容合并到單個(gè)用戶界面中的一種方法馍盟。容器視圖控制器通常用于促進(jìn)導(dǎo)航于置,并基于現(xiàn)有內(nèi)容創(chuàng)建新的用戶界面類型。UIKit中的容器視圖控制器的例子包括UINavigationController贞岭,UITabBarControllerUISplitViewController八毯,所有這些都方便您的用戶界面的不同部分之間的導(dǎo)航。

設(shè)計(jì)自定義容器視圖控制器

幾乎在任何情況下曹步,容器視圖控制器都像其他任何內(nèi)容視圖控制器一樣管理根視圖和一些內(nèi)容宪彩。區(qū)別在于容器視圖控制器從其他視圖控制器獲取其內(nèi)容的一部分休讳。它獲取的內(nèi)容僅限于其他視圖控制器的視圖讲婚,它嵌入在其自己的視圖層次結(jié)構(gòu)中。容器視圖控制器設(shè)置任何嵌入視圖的大小和位置俊柔,但原始視圖控制器仍然管理這些視圖內(nèi)的內(nèi)容筹麸。

在設(shè)計(jì)自己的容器視圖控制器時(shí)活合,請(qǐng)始終了解容器和包含的視圖控制器之間的關(guān)系。視圖控制器的關(guān)系可以幫助告知他們的內(nèi)容應(yīng)該如何顯示在屏幕上物赶,以及你的容器如何在內(nèi)部管理它們白指。在設(shè)計(jì)過程中,問自己以下問題:

1 .容器的作用是什么酵紫?它的孩子扮演什么樣的角色告嘲?
2.多少個(gè)孩子同時(shí)顯示?
3.兄弟視圖控制器之間有什么關(guān)系(如果有的話)奖地?
4.子視圖控制器如何添加到容器或從容器中移除橄唬?
5.孩子的大小或位置能改變嗎?這些變化在什么情況下發(fā)生参歹?
6.容器是否提供任何裝飾或?qū)Ш较嚓P(guān)的視圖仰楚?
7.集裝箱和子公司之間需要什么樣的溝通?容器是否需要向小孩報(bào)告特定事件犬庇,而不是由UIViewController班級(jí)定義的標(biāo)準(zhǔn)事件僧界?
8.容器的外觀是否可以用不同的方式配置?如果是這樣臭挽,怎么樣捂襟?

在定義了各種對(duì)象的角色之后,容器視圖控制器的實(shí)現(xiàn)相對(duì)簡(jiǎn)單欢峰。UIKit唯一的要求就是在容器視圖控制器和任何子視圖控制器之間建立正式的父子關(guān)系笆豁。親子關(guān)系確保孩子們收到任何相關(guān)的系統(tǒng)消息。除此之外赤赊,大部分實(shí)際工作都發(fā)生在包含視圖的布局和管理過程中闯狱,對(duì)于每個(gè)容器來說都是不同的。您可以將視圖放置在容器的內(nèi)容區(qū)域的任何位置抛计,然后根據(jù)需要調(diào)整視圖的大小哄孤。您還可以將自定義視圖添加到視圖層次結(jié)構(gòu)中,以提供修飾或輔助導(dǎo)航吹截。

示例:導(dǎo)航控制器

UINavigationController對(duì)象通過分層數(shù)據(jù)集支持導(dǎo)航瘦陈。導(dǎo)航界面一次顯示一個(gè)子視圖控制器。界面頂部的導(dǎo)航欄顯示數(shù)據(jù)層次結(jié)構(gòu)中的當(dāng)前位置波俄,并顯示后退按鈕以向后移動(dòng)一個(gè)級(jí)別晨逝。向下導(dǎo)航到數(shù)據(jù)層次結(jié)構(gòu)留給子視圖控制器,可以涉及使用表或按鈕懦铺。

視圖控制器之間的導(dǎo)航由導(dǎo)航控制器及其子節(jié)點(diǎn)聯(lián)合管理捉貌。當(dāng)用戶與子視圖控制器的按鈕或表格行交互時(shí),孩子要求導(dǎo)航控制器將新的視圖控制器推入視圖。孩子處理新的視圖控制器的內(nèi)容的配置趁窃,但導(dǎo)航控制器管理過渡動(dòng)畫牧挣。導(dǎo)航控制器還管理導(dǎo)航欄,該導(dǎo)航欄顯示關(guān)閉最上面的視圖控制器的后退按鈕醒陆。

圖5-1顯示了導(dǎo)航控制器及其視圖的結(jié)構(gòu)瀑构。大多數(shù)內(nèi)容區(qū)域由最頂層的子視圖控制器填充,只有一小部分被導(dǎo)航欄占用刨摩。


VCPG_structure-of-navigation-interface_5-1_2x.png

在緊湊和常規(guī)的環(huán)境中寺晌,導(dǎo)航控制器一次只顯示一個(gè)子視圖控制器。導(dǎo)航控制器調(diào)整其子以適應(yīng)可用空間澡刹。

示例:分割視圖控制器

一個(gè)UISplitViewController對(duì)象以主 - 細(xì)節(jié)布局顯示兩個(gè)視圖控制器的內(nèi)容折剃。在這種安排中,一個(gè)視圖控制器(主視圖)的內(nèi)容決定了其他視圖控制器顯示的細(xì)節(jié)像屋。兩個(gè)視圖控制器的可見性是可配置的怕犁,但也受當(dāng)前環(huán)境的支配。在規(guī)則的水平環(huán)境中己莺,分割視圖控制器可以同時(shí)顯示兩個(gè)子視圖控制器奏甫,也可以隱藏主控并根據(jù)需要顯示。在緊湊的環(huán)境中凌受,分割視圖控制器一次只顯示一個(gè)視圖控制器阵子。

圖5-2顯示了在一個(gè)常規(guī)的水平環(huán)境中的分割視圖界面及其視圖的結(jié)構(gòu)。分割視圖控制器本身只有默認(rèn)的容器視圖胜蛉。在這個(gè)例子中挠进,兩個(gè)子視圖是并排顯示的。子視圖的大小是可配置的誊册,主視圖的可見性也是可配置的领突。


VCPG-split-view-inerface_5-2_2x.png

在界面構(gòu)建器中配置容器

要在設(shè)計(jì)時(shí)創(chuàng)建父子容器關(guān)系,請(qǐng)將容器視圖對(duì)象添加到故事板場(chǎng)景中案怯,如圖5-3所示君旦。容器視圖對(duì)象是代表子視圖控制器內(nèi)容的占位符對(duì)象。使用該視圖來調(diào)整和定位與容器中其他視圖相關(guān)的子視圖嘲碱。

container_view_embed_2x.png

當(dāng)您使用一個(gè)或多個(gè)容器視圖加載視圖控制器時(shí)金砍,Interface Builder還會(huì)加載與這些視圖關(guān)聯(lián)的子視圖控制器。孩子必須與父母同時(shí)實(shí)例化麦锯,以便建立適當(dāng)?shù)挠H子關(guān)系恕稠。

如果您不使用Interface Builder設(shè)置父 - 子容器關(guān)系,則必須通過將每個(gè)子項(xiàng)添加到容器視圖控制器來以編程方式創(chuàng)建這些關(guān)系扶欣,如將子視圖控制器添加到您的內(nèi)容中所述鹅巍。

實(shí)現(xiàn)自定義容器視圖控制器

要實(shí)現(xiàn)一個(gè)容器視圖控制器千扶,你必須建立你的視圖控制器和它的子視圖控制器之間的關(guān)系。在嘗試管理任何子視圖控制器的視圖之前昆著,建立這些父子關(guān)系是必需的县貌。這樣做讓UIKit知道你的視圖控制器正在管理孩子的大小和位置术陶。您可以在Interface Builder中創(chuàng)建這些關(guān)系凑懂,或以編程方式創(chuàng)建它們。以編程方式創(chuàng)建父子關(guān)系時(shí)梧宫,您明確地添加和刪除子視圖控制器作為視圖控制器設(shè)置的一部分接谨。

將子視圖控制器添加到您的內(nèi)容
要以編程方式將子視圖控制器合并到內(nèi)容中,請(qǐng)執(zhí)行以下操作塘匣,在相關(guān)的視圖控制器之間創(chuàng)建父子關(guān)系:

調(diào)用addChildViewController:你的容器視圖控制器的方法脓豪。

這個(gè)方法告訴UIKit你的容器視圖控制器現(xiàn)在正在管理子視圖控制器的視圖。

將孩子的根視圖添加到容器的視圖層次結(jié)構(gòu)中忌卤。

一定要記住設(shè)置孩子框架的大小和位置扫夜,作為這個(gè)過程的一部分。

添加任何約束來管理子視圖的大小和位置驰徊。

調(diào)用didMoveToParentViewController:子視圖控制器的方法萝玷。

清單5-1展示了一個(gè)容器如何在其容器中嵌入一個(gè)子視圖控制器咬像。建立父子關(guān)系后,容器設(shè)置其子的框架,并將子視圖添加到自己的視圖層次結(jié)構(gòu)中直奋。設(shè)置子視圖的框架大小很重要,并確保視圖在容器中正確顯示蟀俊。在添加視圖后迅栅,容器調(diào)用didMoveToParentViewController:子視圖的方法,使子視圖控制器有機(jī)會(huì)響應(yīng)視圖所有權(quán)的更改张漂。

清單5-1將一個(gè)子視圖控制器添加到一個(gè)容器

- (void) displayContentController: (UIViewController*) content {
   [self addChildViewController:content];
   content.view.frame = [self frameForContentController];
   [self.view addSubview:self.currentClientView];
   [content didMoveToParentViewController:self];
}

在前面的例子中晶默,注意你只調(diào)用didMoveToParentViewController:子方法。那是因?yàn)樵摲椒槟?code>addChildViewController:調(diào)用孩子的willMoveToParentViewController:方法航攒。您必須didMoveToParentViewController:自己調(diào)用方法的原因是荤胁,只有在將子視圖嵌入到容器的視圖層次結(jié)構(gòu)中之后,方法才能被調(diào)用屎债。

使用自動(dòng)布局時(shí)仅政,在將子對(duì)象添加到容器的視圖層次結(jié)構(gòu)后,在容器和子對(duì)象之間設(shè)置約束盆驹。你的約束應(yīng)該只影響孩子的根視圖的大小和位置圆丹。請(qǐng)勿更改子視圖層次結(jié)構(gòu)中的根視圖或任何其他視圖的內(nèi)容。

刪除子視圖控制器

要從內(nèi)容中刪除子視圖控制器躯喇,請(qǐng)通過執(zhí)行以下操作來刪除視圖控制器之間的父子關(guān)系:

willMoveToParentViewController:用值 調(diào)用孩子的方法nil辫封。

刪除您使用該子視圖的配置的任何約束硝枉。

從容器的視圖層次結(jié)構(gòu)中移除孩子的根視圖。

調(diào)用孩子的removeFromParentViewController方法來完成親子關(guān)系的結(jié)束倦微。

刪除子視圖控制器會(huì)永久切斷父級(jí)和子級(jí)之間的關(guān)系妻味。只有當(dāng)您不再需要引用子視圖控制器時(shí),才能移除子視圖控制器欣福。例如责球,當(dāng)新導(dǎo)航控制器被推入導(dǎo)航堆棧時(shí),導(dǎo)航控制器不會(huì)移除其當(dāng)前的子視圖控制器拓劝。只有當(dāng)它們從堆棧中彈出時(shí)才會(huì)被刪除雏逾。

清單5-2顯示了如何從容器中刪除子視圖控制器。willMoveToParentViewController:使用該值調(diào)用該方法nil使子視圖控制器有機(jī)會(huì)為更改做準(zhǔn)備郑临。該removeFromParentViewController方法還調(diào)用孩子的didMoveToParentViewController:方法栖博,傳遞該方法的值nil。設(shè)置父視圖控制器以nil完成從容器中刪除子視圖厢洞。

清單5-2從容器中刪除一個(gè)子視圖控制器

- (void) hideContentController: (UIViewController*) content {
   [content willMoveToParentViewController:nil];
   [content.view removeFromSuperview];
   [content removeFromParentViewController];
}

子視圖控制器之間的過渡

當(dāng)您想要用另一個(gè)子視圖控制器替換動(dòng)畫時(shí)仇让,將子視圖控制器的添加和刪除合并到轉(zhuǎn)換動(dòng)畫過程中。在動(dòng)畫之前躺翻,確保兩個(gè)子視圖控制器都是你的內(nèi)容的一部分丧叽,但讓當(dāng)前的孩子知道它即將消失。在您的動(dòng)畫中获枝,將新的孩子的視圖移動(dòng)到位并移除舊的孩子的視圖蠢正。完成動(dòng)畫后,完成子視圖控制器的移除省店。

清單5-3顯示了如何使用過渡動(dòng)畫將一個(gè)子視圖控制器交換為另一個(gè)子視圖控制器的示例嚣崭。在這個(gè)例子中,新的視圖控制器被動(dòng)畫為現(xiàn)有的子視圖控制器當(dāng)前占用的矩形懦傍,該控制器被移出屏幕雹舀。動(dòng)畫完成后,完成塊從容器中刪除子視圖控制器粗俱。在這個(gè)例子中说榆,該transitionFromViewController:toViewController:duration:options:animations:completion:方法自動(dòng)更新容器的視圖層次,所以你不需要自己添加和刪除視圖寸认。

清單5-3兩個(gè)子視圖控制器之間的轉(zhuǎn)換

- (void)cycleFromViewController: (UIViewController*) oldVC
               toViewController: (UIViewController*) newVC {
   // Prepare the two view controllers for the change.
   [oldVC willMoveToParentViewController:nil];
   [self addChildViewController:newVC];
 
   // Get the start frame of the new view controller and the end frame
   // for the old view controller. Both rectangles are offscreen.
   newVC.view.frame = [self newViewStartFrame];
   CGRect endFrame = [self oldViewEndFrame];
 
   // Queue up the transition animation.
   [self transitionFromViewController: oldVC toViewController: newVC
        duration: 0.25 options:0
        animations:^{
            // Animate the views to their final positions.
            newVC.view.frame = oldVC.view.frame;
            oldVC.view.frame = endFrame;
        }
        completion:^(BOOL finished) {
           // Remove the old view controller and send the final
           // notification to the new view controller.
           [oldVC removeFromParentViewController];
           [newVC didMoveToParentViewController:self];
        }];
}

管理兒童的外觀更新

在將容器添加到容器后签财,容器會(huì)自動(dòng)將外觀相關(guān)的消息轉(zhuǎn)發(fā)給子容器。這通常是您想要的行為偏塞,因?yàn)樗_保所有事件都能正確發(fā)送唱蒸。但是,有時(shí)默認(rèn)行為可能會(huì)以對(duì)您的容器無意義的順序發(fā)送這些事件灸叼。例如神汹,如果多個(gè)孩子同時(shí)改變其視圖狀態(tài)庆捺,則可能需要合并這些更改,以使外觀回調(diào)都以更合理的順序同時(shí)發(fā)生屁魏。

為了接管外觀回調(diào)的責(zé)任滔以,重寫shouldAutomaticallyForwardAppearanceMethods容器視圖控制器中的方法并返回NO,如清單5-4所示氓拼。返回NOUIKit知道你的容器視圖控制器通知其子的外觀變化你画。

清單5-4禁用自動(dòng)外觀轉(zhuǎn)發(fā)

- (BOOL) shouldAutomaticallyForwardAppearanceMethods {
    return NO;
}

當(dāng)出現(xiàn)外觀轉(zhuǎn)換時(shí),根據(jù)需要調(diào)用子對(duì)象beginAppearanceTransition:animated:或endAppearanceTransition方法披诗。例如撬即,如果您的容器有一個(gè)由child屬性引用的單個(gè)子項(xiàng)立磁,那么您的容器會(huì)將這些消息轉(zhuǎn)發(fā)給子項(xiàng)呈队,如清單5-5所示。

清單5-5當(dāng)容器出現(xiàn)或消失時(shí)轉(zhuǎn)發(fā)外觀消息

-(void) viewWillAppear:(BOOL)animated {
    [self.child beginAppearanceTransition: YES animated: animated];
}
 
-(void) viewDidAppear:(BOOL)animated {
    [self.child endAppearanceTransition];
}
 
-(void) viewWillDisappear:(BOOL)animated {
    [self.child beginAppearanceTransition: NO animated: animated];
}
 
-(void) viewDidDisappear:(BOOL)animated {
    [self.child endAppearanceTransition];
}

建立一個(gè)容器視圖控制器的建議

設(shè)計(jì)唱歧,開發(fā)和測(cè)試新的容器視圖控制器需要時(shí)間宪摧。雖然個(gè)人行為是直截了當(dāng)?shù)模麄€(gè)控制者可能相當(dāng)復(fù)雜颅崩。在實(shí)現(xiàn)自己的容器類時(shí)几于,請(qǐng)考慮以下提示:

只訪問子視圖控制器的根視圖。一個(gè)容器只能訪問每個(gè)孩子的根視圖沿后,也就是孩子view屬性返回的視圖沿彭。它不應(yīng)該訪問任何孩子的其他意見。

子視圖控制器應(yīng)該對(duì)其容器有最少的了解尖滚。子視圖控制器應(yīng)該關(guān)注自己的內(nèi)容喉刘。如果容器允許其行為受到孩子的影響,則應(yīng)該使用委托設(shè)計(jì)模式來管理這些交互漆弄。

首先使用常規(guī)視圖設(shè)計(jì)您的容器睦裳。使用常規(guī)視圖(而不是來自子視圖控制器的視圖)使您有機(jī)會(huì)在簡(jiǎn)化的環(huán)境中測(cè)試布局約束和動(dòng)畫過渡。當(dāng)常規(guī)視圖按預(yù)期工作時(shí)撼唾,將它們交換為您的子視圖控制器的視圖廉邑。

將控制委派給子視圖控制器

容器視圖控制器可以將其自身外觀的某些方面委托給其一個(gè)或多個(gè)子級(jí)。您可以通過以下方式委托控制:

1.讓一個(gè)子視圖控制器確定狀態(tài)欄的樣式倒谷。要委派狀態(tài)欄外觀小孩蛛蒙,覆蓋的一個(gè)或兩個(gè)childViewControllerForStatusBarStyle,并childViewControllerForStatusBarHidden在你的容器視圖控制器的方法渤愁。

  1. 讓孩子指定自己喜歡的尺寸牵祟。具有靈活布局的容器可以使用孩子自己的preferredContentSize財(cái)產(chǎn)來幫助確定孩子的大小。

博客推薦:https://4xx.me

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末猴伶,一起剝皮案震驚了整個(gè)濱河市课舍,隨后出現(xiàn)的幾起案子塌西,更是在濱河造成了極大的恐慌,老刑警劉巖筝尾,帶你破解...
    沈念sama閱讀 212,383評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件捡需,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡筹淫,警方通過查閱死者的電腦和手機(jī)站辉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來损姜,“玉大人饰剥,你說我怎么就攤上這事〈菰模” “怎么了汰蓉?”我有些...
    開封第一講書人閱讀 157,852評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)棒卷。 經(jīng)常有香客問我顾孽,道長(zhǎng),這世上最難降的妖魔是什么比规? 我笑而不...
    開封第一講書人閱讀 56,621評(píng)論 1 284
  • 正文 為了忘掉前任若厚,我火速辦了婚禮,結(jié)果婚禮上蜒什,老公的妹妹穿的比我還像新娘测秸。我一直安慰自己,他們只是感情好灾常,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,741評(píng)論 6 386
  • 文/花漫 我一把揭開白布霎冯。 她就那樣靜靜地躺著,像睡著了一般岗憋。 火紅的嫁衣襯著肌膚如雪肃晚。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,929評(píng)論 1 290
  • 那天仔戈,我揣著相機(jī)與錄音关串,去河邊找鬼。 笑死监徘,一個(gè)胖子當(dāng)著我的面吹牛晋修,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播凰盔,決...
    沈念sama閱讀 39,076評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼墓卦,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了户敬?” 一聲冷哼從身側(cè)響起落剪,我...
    開封第一講書人閱讀 37,803評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤睁本,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后忠怖,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體呢堰,經(jīng)...
    沈念sama閱讀 44,265評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,582評(píng)論 2 327
  • 正文 我和宋清朗相戀三年凡泣,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了枉疼。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,716評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡鞋拟,死狀恐怖骂维,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情贺纲,我是刑警寧澤航闺,帶...
    沈念sama閱讀 34,395評(píng)論 4 333
  • 正文 年R本政府宣布,位于F島的核電站哮笆,受9級(jí)特大地震影響来颤,放射性物質(zhì)發(fā)生泄漏汰扭。R本人自食惡果不足惜稠肘,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,039評(píng)論 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望萝毛。 院中可真熱鬧项阴,春花似錦、人聲如沸笆包。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽庵佣。三九已至歉胶,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間巴粪,已是汗流浹背通今。 一陣腳步聲響...
    開封第一講書人閱讀 32,027評(píng)論 1 266
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留肛根,地道東北人辫塌。 一個(gè)月前我還...
    沈念sama閱讀 46,488評(píng)論 2 361
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像派哲,于是被迫代替她去往敵國(guó)和親臼氨。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,612評(píng)論 2 350

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