在容器视图中交换子视图

26

ContainerView成为具有两个子内容视图的父容器视图:NavigationViewContentView

View Layout示例

我想能够用另一个视图替换ContentView的控制器。例如,将主页控制器与新闻页面控制器交换。目前,我唯一能想到的方法是使用委托来告诉ContainerView我要切换视图。这似乎是一种笨拙的方法,因为ContainerViewController最终会有许多特殊委托用于所有子视图。

这也需要与NavigationView通信,后者具有有关当前在ContentView中的哪个视图的信息。例如:如果用户在新闻页面上,则导航视图中的导航栏将显示当前选择的新闻按钮。

A问题:是否有一种方法可以在没有委托方法调用ContainerView本身的情况下交换ContentView中的控制器?我希望以编程方式实现这一点(无Storyboard)。

B问题:如何在没有委托调用的情况下从NavigationView中交换ContentView中的控制器?我希望以编程方式实现这一点(无Storyboard)。


2
你看过UIPageViewController了吗?“页面视图控制器允许用户在内容的页面之间导航,其中每个页面由其自己的视图控制器对象管理。” - shadowhorst
3个回答

41

如果您有子视图并且这些子视图有自己的视图控制器,您应该遵循自定义容器控制器模式。请参阅创建自定义容器视图控制器了解更多信息。

假设您已经遵循了自定义容器模式,当您想要更改"内容视图"的子视图控制器(及其相关视图)时,您可以通过编程方式进行操作,例如:

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. 定义一个协议:
  2. @protocol ContainerParent <NSObject>
    
    - (void)changeContentTo:(UIViewController *)controller;
    
    @end
    
    定义父控制器符合这个协议,例如:
    #import <UIKit/UIKit.h>
    #import "ContainerParent.h"
    
    @interface ViewController : UIViewController <ContainerParent>
    
    @end
    
  3. 在父控制器中实现changeContentTo方法(与上述大致相同):

  4. - (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];
                                }];
    }
    
  5. 现在子控制器可以使用此协议来引用iOS为您提供的self.parentViewController属性:

  6. - (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];
    }
    

1
@orbv 这段代码只在父控制器中,因此没有重复的问题。也许我没有理解你的问题。 - Rob
1
Xcode 5现在只允许在添加容器视图时使用嵌入式segue。如果正在使用自动布局,我也会避免直接设置框架。 - Abizern
1
@Abizern 同意。如果目标是 iOS 6 及以上版本,并且使用故事板,那就是我会做的。但 OP 说他想以编程方式而不是故事板来完成。 - Rob
1
@orbv 那么你需要为此定义一个协议,然后让子视图控制器调用父视图控制器中的适当方法。这样,协议定义了一个漂亮干净的接口,但是在所有子控制器中不会重复使用复杂的自定义容器调用。请参见修订后的答案。 - Rob
2
@Rob 我明白你的意思,再读一遍问题后我明白了。只是随便提一下而已。 ;) - Abizern
显示剩余2条评论

2

对于这种类型的过渡,您也可以使用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、转场等。


-1

你似乎有些困惑了。一个 contentView(假设是 UIView)并不“包含”你要交换的控制器。一个 UIViewController 处理它的 UIView。我认为你需要一个父子视图控制器设置。

一个单一的父视图控制器,可以处理子视图控制器,每个子视图控制器在屏幕上显示时,你都可以相应地调整你的 UIView 和内容。请参阅下面的 Apple 文档。

容器编程 - Apple 文档


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接