【问题标题】:Change Container View Content with Tabs in iOS在 iOS 中使用选项卡更改容器视图内容
【发布时间】:2013-04-26 03:53:22
【问题描述】:

我正在尝试制作一个跨越三个选项卡的表单。您可以在下面的屏幕截图中看到选项卡的位置。当用户点击选项卡时,容器视图应该更新并显示我拥有的特定视图控制器。

选项卡 1 = 视图控制器 1

选项卡 2 = 视图控制器 2

选项卡 3 = 视图控制器 3

上面显示的视图控制器具有类 PPAddEntryViewController.m。我在这个类中为容器视图创建了一个出口,现在有一个容器视图属性:

@property (weak, nonatomic) IBOutlet UIView *container;

我的标签页也准备好了 IBActions:

- (IBAction)tab1:(id)sender {
  //...
}
- (IBAction)tab2:(id)sender {
  //...
}
- (IBAction)tab3:(id)sender {
  //...
}

如何在这些 IBActions 中设置容器以更改 Container View 持有的视图控制器?

除其他几件事外,这是我尝试过的:

UIViewController *viewController1 = [self.storyboard instantiateViewControllerWithIdentifier:@"vc1"];
_container.view = viewController1;

...但它不起作用。提前致谢。

【问题讨论】:

标签: ios xcode-storyboard container-view


【解决方案1】:

使用 Storyboard 进行切换、是否自动布局、某种按钮以及一系列子视图控制器

您希望将容器视图添加到您的视图中,并在按下“切换”子视图控制器的按钮时触发相应的 segue 并执行正确的设置工作。

在 Storyboard 中,您只能将一个 Embed Segue 连接到容器视图。因此,您创建了一个中间处理控制器。制作嵌入 segue 并给它一个标识符,例如 EmbededSegueIdentifier。

在您的父视图控制器中,连接按钮或您想要并保留的任何内容都是在准备 segue 中引用您的子视图控制器。一旦父视图控制器加载,segue 就会被触发。

父视图控制器

@property (weak, nonatomic) MyContainerViewController *myContainerViewController;

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    if ([segue.identifier isEqualToString:@"EmbeddedSegueIdentifier"]) {
        self.myContainerViewController = segue.destinationViewController;
    }
}

您应该很容易将按下的按钮委托给您的容器控制器。

容器控制器

接下来的代码部分是从几个来源借用的,但关键的变化是使用了自动布局而不是显式框架。没有什么可以阻止您简单地将行 [self addConstraintsForViewController:] 更改为 viewController.view.frame = self.view.bounds。在 Storyboard 中,这个 Container View Controller 不再做任何与目标子视图控制器分离的事情。

- (void)viewDidLoad
{
    [super viewDidLoad];

    NSLog(@"%s", __PRETTY_FUNCTION__);

    [self performSegueWithIdentifier:@"FirstViewControllerSegue" sender:nil];
}

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    UIViewController *destinationViewController = segue.destinationViewController;

    if ([self.childViewControllers count] > 0) {
        UIViewController *fromViewController = [self.childViewControllers firstObject];
        [self swapFromViewController:fromViewController toViewController:destinationViewController];
    } else {
        [self initializeChildViewController:destinationViewController];
    }
}

- (void)initializeChildViewController:(UIViewController *)viewController
{
    [self addChildViewController:viewController];
    [self.view addSubview:viewController.view];
    [self addConstraintsForViewController:viewController];

    [viewController didMoveToParentViewController:self];
}

- (void)swapFromViewController:(UIViewController *)fromViewController toViewController:(UIViewController *)toViewController
{
    [fromViewController willMoveToParentViewController:nil];
    [self addChildViewController:toViewController];
    [self transitionFromViewController:fromViewController toViewController:toViewController duration:0.2f options:UIViewAnimationOptionTransitionCrossDissolve animations:nil completion:^(BOOL finished) {
        [self addConstraintsForViewController:toViewController];
        [fromViewController removeFromParentViewController];
        [toViewController didMoveToParentViewController:self];
    }];
}

- (void)addConstraintsForViewController:(UIViewController *)viewController
{
    UIView *containerView = self.view;
    UIView *childView = viewController.view;
    [childView setTranslatesAutoresizingMaskIntoConstraints:NO];
    [containerView addSubview:childView];

    NSDictionary *views = NSDictionaryOfVariableBindings(childView);
    [containerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[childView]|"
                                                                          options:0
                                                                          metrics:nil
                                                                            views:views]];
    [containerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[childView]|"
                                                                          options:0
                                                                          metrics:nil
                                                                            views:views]];
}



#pragma mark - Setters

- (void)setSelectedControl:(ViewControllerSelectionType)selectedControl
{
    _selectedControl = selectedControl;

    switch (self.selectedControl) {
        case kFirstViewController:
            [self performSegueWithIdentifier:@"FirstViewControllerSegue" sender:nil];
            break;
        case kSecondViewController:
            [self performSegueWithIdentifier:@"SecondViewControllerSegue" sender:nil];
            break;
        default:
            break;
    }
}

自定义转场

您需要的最后一件事是自定义 segue,它什么都不做,使用从容器视图控制器调用的适当 segue 标识符前往每个目的地。如果您不输入一个空的 perform 方法,应用程序将崩溃。通常你可以在这里做一些自定义的过渡动画。

@implementation SHCDummySegue

@interface SHCDummySegue : UIStoryboardSegue

@end

- (void)perform
{
    // This space intentionally left blank
}

@end

【讨论】:

  • 非常不错的通用解决方案,不仅适用于替换 UITabbarController!
【解决方案2】:

我最近找到了完美示例代码,可以满足我的尝试。它包括 Storyboard 实现和所有相关的 segues 和代码。真的很有帮助。

https://github.com/mhaddl/MHCustomTabBarController

【讨论】:

  • 缺少免责声明,并且是一个链接,只能回答两者
【解决方案3】:

更新:正如您之前发现的,UITabBarController 是推荐的方式。如果您想要自定义高度,这是一个好的开始:My way of customizing UITabBarController's tabbar - Stackoverflow answer

从 iOS 5+ 开始,您可以通过此 API 自定义外观; UIAppearance Protocol Reference。这是一个很好的教程:How To Customize Tab Bar Background and Appearance

实现您正在寻找的最明显的方法是简单地管理 3 个不同的容器(它们是简单的 UIView)并实现它们中的每一个来保存每个选项卡所需的任何内容视图(使用容器)。

下面是一个示例,说明使用不同容器可以实现什么:

这些容器“交换”当然可以动画。关于您的自我回答,您可能选择了正确的方法。

【讨论】:

  • 他没有使用标签栏控制器 AFAIK,这就是容器视图存在的原因
  • @Daij-Djan 我想我没有提到标签栏控制器。我只看到标签。关键是,容器视图并不意味着像他想要的那样交换它的嵌入视图控制器。
  • 我想过在同一个视图控制器中显示/隐藏所有视图,但作为 Storyboard 用户,如果我可以将 3 个视图中的每一个都保留为单独的视图控制器会更方便。这将允许我分别设置每个视图控制器的样式并能够查看其布局(隐藏视图不会那么方便)。
  • @Roger: ": 容器视图每次都会获得一个新的父级" -> 只有在涉及切换容器的 tabviewcontroller 时才不会。这里的想法是切换容器内容
  • @CliftonLabrum 不确定是不是你投了反对票,但让我澄清一下我的答案,这样可能会更容易理解。我还将用我所说的具体例子来更新我的答案。
【解决方案4】:

我通过使用 UITabBarController 来实现这一点。为了使用自定义选项卡,我必须继承 TabBarController 并将按钮添加到代码中的控制器。然后我监听按钮上的点击事件并为每个选项卡设置selectedIndex

这很简单,但对于像 3 个标签这样简单的东西,在我的 Storyboard 中却是一大堆垃圾。

【讨论】:

  • 是的,这就是它的本意......虽然我也经常使用像我概述的那样切换视图的方法......
  • 我尝试使用手动方法来切换视图,就像你提到的那样,我实际上更喜欢它。然后只有一个导航控制器在玩,一个保存按钮,我的故事板中的冗余少了很多。我想我还是会同意你提到的。我看不到你的答案了,但谢谢你的插话!
  • 我的答案在底部,并以“有一个成员变量...”开头:)
【解决方案5】:

有一个成员变量来保存 viewController:

UIViewController *selectedViewController;

现在在 IBActions 中,切换它和视图。例如

- (IBAction)tab1:(id)sender {
     UIViewController *viewController1 = [self.storyboard instantiateViewControllerWithIdentifier:@"vc1"];

     _container.view = viewController1.view;
     selectedViewController = viewController1;
}

确实出现了触发视图,并且调用了 removeChildViewController、didMoveToParent、addChildViewController、didMoveToParent

【讨论】:

  • 你能帮我理解selectedViewController = viewController1;的用途吗?
  • 我仍然无法使其正常工作。有什么方法可以组合一个快速的 Xcode 项目,在 Storyboard 中的两个单独的 viewController 之间交换 UIContainerView 的内容?我想我已经正确连接了它,但我无法让 ControllerView 中的视图发生变化。
  • 此解决方案有效!只需确保保存到存储 selectedViewController ,如本示例所示。我跳过了那部分,并不断出现记忆缺失。那是因为它是在 tab1 完成后发布的,这使我的新控制器根本无法工作。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-06-08
  • 2018-04-27
  • 1970-01-01
相关资源
最近更新 更多