web-dev-qa-db-ja.com

コンテナビューで子ビューを交換する

ContainerViewを、2つの子コンテンツビューNavigationViewおよびContentViewを持つ親コンテナビューとします。

Example of View Layout

ContentViewのコントローラーを別のビューと交換できるようにしたいと思います。たとえば、ホームページコントローラをニュースページコントローラと交換します。現在、これを行うには、デリゲートを使用してContainerViewにビューを切り替えたいことを伝えるしかありません。これは、ContainerViewControllerがすべてのサブビューの特別なデリゲートの束を持つことになるため、これを行うためのずさんな方法のように見えます。

これは、現在NavigationViewにあるビューに関する情報を持つContentViewとも通信する必要があります。たとえば、ユーザーがニュースページを開いている場合、ナビゲーションビュー内のナビゲーションバーには、ニュースボタンが現在選択されていることが表示されます。

質問A:デリゲートメソッドがContentView自体を呼び出さずにContainerViewでコントローラーを交換する方法はありますか?これをプログラムで実行したい(ストーリーボードなし)。

質問B:デリゲートコールなしでContentViewからNavigationViewのコントローラーをどのように交換できますか?これをプログラムで実行したい(ストーリーボードなし)。

23
Alex

独自のビューコントローラーを持つ子ビューがある場合、カスタムコンテナーコントローラーパターンに従う必要があります。詳細については、 カスタムコンテナビューコントローラの作成 を参照してください。

カスタムコンテナーパターンに従っていると想定して、「コンテンツビュー」の子ビューコントローラー(およびそれに関連付けられているビュー)を変更する場合は、次のようにプログラムで行います。

UIViewController *newController = ... // instantiate new controller however you want
UIViewController *oldController = ... // grab the existing controller for the current "content view"; perhaps you maintain this in your own ivar; perhaps you just look this up in self.childViewControllers

newController.view.frame = oldController.view.frame;

[oldController willMoveToParentViewController:nil];
[self addChildViewController:newController];         // incidentally, this does the `willMoveToParentViewController` for the new controller for you

[self transitionFromViewController:oldController
                  toViewController:newController
                          duration:0.5
                           options:UIViewAnimationOptionTransitionCrossDissolve
                        animations:^{
                            // no further animations required
                        }
                        completion:^(BOOL finished) {
                            [oldController removeFromParentViewController]; // incidentally, this does the `didMoveToParentViewController` for the old controller for you
                            [newController didMoveToParentViewController:self];
                        }];

このようにすると、コンテンツビューのコントローラーとのデリゲートプロトコルインターフェイスは必要ありません(iOSが カスタムコンテナーで子ビューコントローラーを管理する メソッドを使用して既に提供しているものを除きます)。


ちなみに、これは、そのコンテンツビューに関連付けられた最初の子コントローラーが次のように追加されていることを前提としています。

UIViewController *childController = ... // instantiate the content view's controller any way you want
[self addChildViewController:childController];
childController.view.frame = ... // set the frame any way you want
[self.view addSubview:childController.view];
[childController didMoveToParentViewController:self];

子コントローラーが親にコンテンツビューに関連付けられているコントローラーを変更するように指示する場合は、次のようにします。

  1. このためのプロトコルを定義します。

    @protocol ContainerParent <NSObject>
    
    - (void)changeContentTo:(UIViewController *)controller;
    
    @end
    
  2. このプロトコルに準拠するように親コントローラーを定義します。例:

    #import <UIKit/UIKit.h>
    #import "ContainerParent.h"
    
    @interface ViewController : UIViewController <ContainerParent>
    
    @end
    
  3. 親コントローラーにchangeContentToメソッドを実装します(上記で概説したとおり)。

    - (void)changeContentTo:(UIViewController *)controller
    {
        UIViewController *newController = controller;
        UIViewController *oldController = ... // grab reference of current child from `self.childViewControllers or from some property where you stored it
    
        newController.view.frame = oldController.view.frame;
    
        [oldController willMoveToParentViewController:nil];
        [self addChildViewController:newController];
    
        [self transitionFromViewController:oldController
                          toViewController:newController
                                  duration:1.0
                                   options:UIViewAnimationOptionTransitionCrossDissolve
                                animations:^{
                                    // no further animations required
                                }
                                completion:^(BOOL finished) {
                                    [oldController removeFromParentViewController];
                                    [newController didMoveToParentViewController:self];
                                }];
    }
    
  4. そして、子コントローラーは、iOSが提供するself.parentViewControllerプロパティを参照してこのプロトコルを使用できるようになりました。

    - (IBAction)didTouchUpInsideButton:(id)sender
    {
        id <ContainerParent> parentViewController = (id)self.parentViewController;
        NSAssert([parentViewController respondsToSelector:@selector(changeContentTo:)], @"Parent must conform to ContainerParent protocol");
    
        UIViewController *newChild = ... // instantiate the new child controller any way you want
        [parentViewController changeContentTo:newChild];
    }
    
38
Rob

このような種類の遷移には、UIView.animateWith...アニメーションを使用することもできます。

たとえば、rootContainerViewがコンテナであり、contentViewControllerが現在コンテナ内でアクティブなコントローラであると仮定すると、

func setContentViewController(contentViewController:UIViewController, animated:Bool = true) {
    if animated == true {
        addChildViewController(contentViewController)
        contentViewController.view.alpha = 0
        contentViewController.view.frame = rootContainerView.bounds
        rootContainerView.addSubview(contentViewController.view)
        self.contentViewController?.willMoveToParentViewController(nil)

        UIView.animateWithDuration(0.3, animations: {
            contentViewController.view.alpha = 1
            }, completion: { (_) in
                contentViewController.didMoveToParentViewController(self)

                self.contentViewController?.view.removeFromSuperview()
                self.contentViewController?.didMoveToParentViewController(nil)
                self.contentViewController?.removeFromParentViewController()
                self.contentViewController = contentViewController
        })
    } else {
        cleanUpChildControllerIfPossible()

        contentViewController.view.frame = rootContainerView.bounds
        addChildViewController(contentViewController)
        rootContainerView.addSubview(contentViewController.view)
        contentViewController.didMoveToParentViewController(self)
        self.contentViewController = contentViewController
    }
}

// MARK: - Private

private func cleanUpChildControllerIfPossible() {
    if let childController = contentViewController {
        childController.willMoveToParentViewController(nil)
        childController.view.removeFromSuperview()
        childController.removeFromParentViewController()
    }
}

これにより、シンプルなフェードアニメーションが提供されます。また、UIViewAnimationOptions、トランジションなどを試すこともできます。

2
gbk