【问题标题】:Swapping child views in a container view在容器视图中交换子视图
【发布时间】:2013-10-10 08:51:05
【问题描述】:

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

我希望能够将ContentView 的控制器换成另一个视图。例如,将主页控制器与新闻页面控制器交换。目前,我能想到的唯一方法是使用委托告诉ContainerView 我想切换视图。这似乎是一种草率的做法,因为ContainerViewController 最终会为所有子视图拥有一堆特殊代表。

这还需要与NavigationView 进行通信,该NavigationView 具有有关当前在ContentView 中的视图的信息。例如:如果用户在新闻页面上,导航视图中的导航栏将显示当前选择了新闻按钮。

问题 A: 有没有办法在没有调用ContainerView 本身的委托方法的情况下交换ContentView 中的控制器?我想以编程方式执行此操作(无情节提要)。

问题 B: 如何在没有委托调用的情况下从NavigationView 交换ContentView 中的控制器?我想以编程方式执行此操作(无情节提要)。

【问题讨论】:

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

标签: ios objective-c animation transitions uicontainerview


【解决方案1】:

当您的子视图拥有自己的视图控制器时,您应该遵循自定义容器控制器模式。请参阅Creating Custom Container View Controllers 了解更多信息。

假设您遵循自定义容器模式,当您想要更改“内容视图”的子视图控制器(及其关联视图)时,您可以通过以下方式以编程方式执行此操作:

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 已经通过 Managing Child View Controllers in a Custom Container 方法提供的接口)。


顺便说一句,这假设与该内容视图关联的初始子控制器是这样添加的:

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

【讨论】:

  • @orbv 此代码仅在父控制器中,因此不涉及重复。也许我不明白你的问题。
  • Xcode 5 只允许您在添加容器视图时使用嵌入转场。如果使用自动布局,我也会避免直接设置框架。
  • @Abizern 同意。如果针对 iOS 6 及更高版本,并使用情节提要,这就是我要做的。但 OP 说他想在没有情节提要的情况下以编程方式完成。
  • @orbv 然后你会为此定义一个协议,然后让孩子在父视图控制器中调用适当的方法。这样,协议定义的漂亮干净的接口,但不会在所有子控制器中重复复杂的自定义容器调用。请参阅修改后的答案。
  • @Rob 再次阅读问题后,我明白你的意思了。不过,只是把它扔在那里。 ;)
【解决方案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,过渡等。

【讨论】:

    【解决方案3】:

    你好像有点糊涂了。 contentView(假设UIView)不“包含”您将换出的控制器。 UIViewController 处理它的 UIView。在我看来,您需要一个父子视图控制器设置。

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

    Container Programming - Apple Documentation

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-06-20
      • 2019-03-01
      • 2019-04-08
      • 1970-01-01
      • 1970-01-01
      • 2014-09-14
      • 1970-01-01
      • 2016-06-12
      相关资源
      最近更新 更多