遇到的問題
我在寫程序的時候碰到這樣一個簡單的需求,用戶點擊“我的XX”這樣的功能時候,需要判斷當前用戶是否已經登錄,如果已經登錄了,則顯示該用戶的相關信息並且可以切換到更多界面:
如果沒有登錄,則顯示登錄界面並且可以選擇登錄還是注冊:
後來繼續了解到其實不止一個功能需要有這樣的需求,任何需要登錄後才能進行的功能,當用戶點擊時,都需要做這樣的判斷。
那我怎樣才能把上述兩種情況有機地結合在一起,並且能在多個地方復用呢?
當時的第一想法是在代碼裡根據當前用戶登錄還是不登錄手動替換NavigationController的RootViewController,但是我目前的程序完全是基於Storyboard來寫的,因此界面的流轉關系是完全體現在Storyboard上的。我想這個功能也能繼續維持這樣的狀態,能在Storyboard上清晰地體現出來(這裡不討論Storyboard的優劣,只是想說明工程代碼的表現形式要一致)。
由於年少不懂事,當時以為只要做出下圖的這種關系就可以了,當然很快就發現這是不可能的
後來發現其實 NavigationController內部也是實現了類似於ContainerViewController的機制 (可能需FQ),所以只需要替換ContainerViewController的內容,自然也就替換了相應的界面。
多分支NavigationController的方法
根據上述的討論,具體的方案已經呼之欲出了(關於具體的自定義ContainerViewController的技術細節就不再闡述了,上面的鏈接已經解釋的很清楚了,這裡主要是討論如何使之應用到我們的場景中)。
我是先自己在Storyboard上建立一個RootViewController,名字暫且定為ConditionContainerViewController(具體代碼後面會介紹到),如下圖:
然後再繼承UIStoryboardSegue,新建一個Segue,名字暫且定為ConditionShowSegue:
- (void)perform { UIViewController *srcVC = self.sourceViewController; UIViewController *destVC = self.destinationViewController; [srcVC addChildViewController:destVC]; [srcVC.view addSubview:destVC.view]; destVC.view.frame = CGRectMake(0, 0, CGRectGetWidth(srcVC.view.frame), CGRectGetHeight(srcVC.view.frame)); [destVC didMoveToParentViewController:srcVC]; }
如果上面代碼暫時不能理解,請回到之前發的鏈接再仔細理解裡面的內容(我當時也是看了好幾遍,呵呵)。
隨後就可以在Storyboard中使用這個Segue了,可以看到菜單裡多了一種“Condition Show”:
用新的Segue來連接之前在本文一開始就展示的兩段分開的業務線,使之結合在一起
然後分別定義這兩個Segue的Identifier為“ShowNeedLogin”以及“ShowLoggedIn”。
接下來我們再回過頭來看ConditionContainerViewController的代碼,其實主要代碼非常簡單就是重載viewWillAppear:這個方法
- (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; if (isLogin) { [self performSegueWithIdentifier:@"ShowHasLogined" sender:self]; } else { [self performSegueWithIdentifier:@"ShowLoggedIn" sender:self]; } }
isLogin這個你可以使用各種方法來實現,比如自定義一個變量或者全局有一個用戶管理類來暴露一個isLogin屬性等。
其實目前程序已經可以根據你當前登錄的情況來自動切換展示的界面了,但是現在還有一個問題,登錄成功之後又怎麼跳回已經登錄的界面的?只要在需要登錄成功後調用如下代碼:
// 普通情況下直接調用popToRootViewControllerAnimated即可 // 然後ConditionContainerViewController會通過viewWillAppear來判斷 NSArray *poppedViewcontrollers = [self popToRootViewControllerAnimated:animated]; // 但是如果是從上面那個圖的Need Login這個界面返回,這個時候已經在RootViewController了 // 因此需要手動調用viewWillAppear if (poppedViewcontrollers == nil) { [[self.viewControllers firstObject] viewWillAppear:YES]; }
好了,接下來我們還需要一些清理代碼,在ConditionContainerViewController寫入以下函數:
@property (nonatomic, strong) UIViewController *lastViewController; ... - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { if (segue.destinationViewController != self.lastViewController) { [self.lastViewController willMoveToParentViewController:nil]; [self.lastViewController.view removeFromSuperview]; [self.lastViewController removeFromParentViewController]; } self.lastViewController = segue.destinationViewController; }
這個清理代碼應該有更好地方來寫,目前我只能想到放在這裡,希望大家可以給一些建議。
當然,一開始我也提到這種方法是要可以復用的,且在Storyboard上能清晰地表示出來,最後我就展示一張簡單的Storyboard來說明: