自定義容器視圖控制器總結

自定義容器視圖控制器總結

同時發布于知乎

1.Apple文檔對容器視圖器的解釋

容器視圖控制器是將來自多個視圖控制器的內容合并到單個用戶界面中的一種方法。容器視圖控制器通常用于導航,并基于現有內容創建新的用戶界面類型。UIKit中的容器視圖控制器的例子包括UINavigationController,UITabBarController和UISplitViewController,所有這些都方便您的用戶界面的不同部分之間進行交互.

2.設計自定義容器視圖控制器

在設計自己的容器視圖控制器時,明確容器角色以及容器和包含的視圖控制器之間的關系。在設計過程中,思考如下問題:

1.容器是什么角色?承擔哪些職責?
2.有哪些Chil-VC? 他們之間是什么關系?
3.子視圖控制器如何添加到容器或從容器中移除?4.需要手動控制child-vc生命周期嗎? Apprease
5.過度動畫如何設計?
6.需要支持轉屏嗎?
7.容器與Child VC 以及 Child VC 間 ,他們該如何通信

3.容器相關知識點

3.1掌握 UIViewController 生命周期

3.2 添加子視圖控制器

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

解釋:

1)調用容器視圖控制器的addChildViewController:,此方法是將子視圖控制器添加到容器視圖控制器,告訴UIKit父視圖控制器現在要管理子視圖控制器和它的視圖。
 2)調用 addSubview: 方法,將子視圖控制器的根視圖加在父視圖控制器的視圖層級上。這里需要設置子視圖控制器中根視圖的位置和大小。
 3)布局子視圖控制器的根視圖。
 4)調用 didMoveToParentViewController:,告訴子視圖控制器其根視圖的被持有情況。也就是需要先把子視圖控制器的根視圖添加在父視圖中的視圖層級中。

在調用 addChildViewController: 時,系統會先調用 willMoveToParentViewController: 
然后再將子視圖控制器添加到父視圖控制器中。
但是系統不會自動調用 didMoveToParentViewController: 方法需要手動調用
為何呢?
視圖控制器是有轉場動畫的,動畫完成后才應該去調用 didMoveToParentViewController: 方法。

3.3 移除子視圖控制器

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

解釋:

1)調用子視圖控制器的willMoveToParentViewController:,參數為 nil,讓子視圖做好被移除的準備。
 2)移除子視圖控制器的根視圖在添加時所作的任何的約束布局。
 3)調用 removeFromSuperview 將子視圖控制器的根視圖從視圖層次結構中移除。
 4)調用 removeFromParentViewController 來告知結束父子關系。
 5)在調用 removeFromParentViewController 時會調用子視圖控制器的 didMoveToParentViewController: 方法,參數為 nil。

3.4 子視圖控制器之間的過渡動畫

-(void)transitionFromViewController:(UIViewController *)fromViewController
                   toViewController:(UIViewController *)toViewController 
                           duration:(NSTimeInterval)duration
                            options:(UIViewAnimationOptions)options
                         animations:(void (^)(void))animations
                         completion:(void (^)(BOOL finished))completion;

*Note
函數內部系統會會自動添加新的視圖和移除之前的視圖

具體事例:

基本原理:
當需要子視圖控制器過渡到另一個視圖控制器的動畫,結合子視圖控制器的添加和刪除到過渡動畫過程。在動畫之前,確保兩個子視圖控制器是內容的一部分,讓當前的子視圖消失。在動畫過程中,移動新子視圖控制器到相應的位置并刪除舊的子視圖控制器。在動畫完成之后,刪除子視圖控制器。

// 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];
        }];

上面的事例有一個缺陷:無法結合Autolayout來布局,原因

// XXX We can't add constraints here because the view is not yet in the view hierarchy
    // animation setup 

代碼二次改造:

- (void) performTransitionFromViewController:(UIViewController*)fromVc toViewController:(UIViewController*)toVc {

    [fromVc willMoveToParentViewController:nil];
    [self addChildViewController:toVc];

    UIView *toView = toVc.view;
    UIView *fromView = fromVc.view;

    [self.containerView addSubview:toView];

    // TODO: set initial layout constraints here

    [self.containerView layoutIfNeeded];

    [UIView animateWithDuration:.25
                          delay:0
                        options:0
                     animations:^{

                         // TODO: set final layout constraints here

                         [self.containerView layoutIfNeeded];

                     } completion:^(BOOL finished) {
                         [toVc didMoveToParentViewController:self];
                         [fromView removeFromSuperview];
                         [fromVc removeFromParentViewController];
                     }];
}

3.5 appearance callbacks的傳遞

////設置為NO,屏蔽對childViewController的生命周期函數的自動調用,改為手動控制
- (BOOL)shouldAutomaticallyForwardAppearanceMethods {
    return NO;
}

容器控制器就要在子控制出現和消失時通知子控制器,
分別通過調用子控制器的 
beginAppearanceTransition:animated: 
&&
endAppearanceTransition():
實現,不需要直接調用子控制器的
 viewWillAppear:
 viewDidAppear:
 viewWillDisappear:
 viewDidDisappear: 方法

 另外注意的是:
 begin和end必須成對出現
 [content beginAppearanceTransition:YES animated:animated]觸發content的viewWillAppear,
 [content beginAppearanceTransition:NO animated:animated]觸發content的viewWillDisappear,
 和他們配套的[content endAppearanceTransition]
 分別觸發viewDidAppear和viewDidDisappear。

3.6 rotation callbacks的傳遞

rotation callbacks 一般情況下只需要關心三個方法 :
willRotateToInterfaceOrientation:duration:
在旋轉開始前,此方法會被調用;
willAnimateRotationToInterfaceOrientation:duration: 
此方法的調用在旋轉動畫block的內部,也就是說在此方法中的代碼會作為旋轉animation block的一部分;
didRotateFromInterfaceOrientation:
此方法會在旋轉結束時被調用。
而作為view controller container 就要肩負起旋轉的決策以及旋轉的callbacks的傳遞的責任。

禁用方式:

禁掉默認調用需要重寫兩個方法 
shouldAutorotate:
supportedInterfaceOrientations:

前者決定再旋轉的時候是否去根據supportedInterfaceOrientations所支持的取向來決定是否旋轉;
假如shouldAutorotate返回YES的時候,才會去調用supportedInterfaceOrientations檢查當前view controller支持的取向,
如果當前取向在支持的范圍中,則進行旋轉,
如果不在則不旋轉;
而當shouldAutorotate返回NO的時候,則根本不會去管supportedInterfaceOrientations這個方法,反正是不會跟著設備旋轉就是了。

在編寫的容器組過程中,先去檢查你的child view controller對橫豎屏的支持情況,以便容器自己決策在橫豎屏旋轉時候是否支持當前的取向

- (BOOL)shouldAutorotate {
    UIViewController *visibleViewController ;
    if (visibleViewController != self && [visibleViewController respondsToSelector:@selector(shouldAutorotate)]) {
        return [visibleViewController shouldAutorotate];
    }
    return YES;
}

- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
    UIViewController *visibleViewController = ;
    if (visibleViewController != self && [visibleViewController respondsToSelector:@selector(supportedInterfaceOrientations)]) {
        return [visibleViewController supportedInterfaceOrientations];
    }
    return self.supportedOrientationMask;
}

3.6 其他

1.重載 childViewControllerForStatusBarStyle 屬性,返回相應的子控制器,讓子控制器決定狀態欄樣式。當這個屬性發生變化,調用 setNeedsStatusBarAppearanceUpdate() 方法更新狀態欄樣式。

2.容器控制器可以用子控制器的 preferredContentSize 屬性決定子控制器 view 的大小。

參考

1.containing-viewcontrollers
2.Apple文檔
3.Autolayout

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。