ContainerView
を、2つの子コンテンツビューNavigationView
およびContentView
を持つ親コンテナビューとします。
ContentView
のコントローラーを別のビューと交換できるようにしたいと思います。たとえば、ホームページコントローラをニュースページコントローラと交換します。現在、これを行うには、デリゲートを使用してContainerView
にビューを切り替えたいことを伝えるしかありません。これは、ContainerViewController
がすべてのサブビューの特別なデリゲートの束を持つことになるため、これを行うためのずさんな方法のように見えます。
これは、現在NavigationView
にあるビューに関する情報を持つContentView
とも通信する必要があります。たとえば、ユーザーがニュースページを開いている場合、ナビゲーションビュー内のナビゲーションバーには、ニュースボタンが現在選択されていることが表示されます。
質問A:デリゲートメソッドがContentView
自体を呼び出さずにContainerView
でコントローラーを交換する方法はありますか?これをプログラムで実行したい(ストーリーボードなし)。
質問B:デリゲートコールなしでContentView
からNavigationView
のコントローラーをどのように交換できますか?これをプログラムで実行したい(ストーリーボードなし)。
独自のビューコントローラーを持つ子ビューがある場合、カスタムコンテナーコントローラーパターンに従う必要があります。詳細については、 カスタムコンテナビューコントローラの作成 を参照してください。
カスタムコンテナーパターンに従っていると想定して、「コンテンツビュー」の子ビューコントローラー(およびそれに関連付けられているビュー)を変更する場合は、次のようにプログラムで行います。
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];
子コントローラーが親にコンテンツビューに関連付けられているコントローラーを変更するように指示する場合は、次のようにします。
このためのプロトコルを定義します。
@protocol ContainerParent <NSObject>
- (void)changeContentTo:(UIViewController *)controller;
@end
このプロトコルに準拠するように親コントローラーを定義します。例:
#import <UIKit/UIKit.h>
#import "ContainerParent.h"
@interface ViewController : UIViewController <ContainerParent>
@end
親コントローラーに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];
}];
}
そして、子コントローラーは、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];
}
このような種類の遷移には、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
、トランジションなどを試すこともできます。