在iOS中使用选项卡更改容器视图内容

16

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

view controller

选项卡 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 中设置容器以更改容器视图所持有的视图控制器?

除其他一些尝试外,以下是我尝试过的:

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

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


请访问以下链接,可能会有所帮助:http://www.cocoanetics.com/2012/04/containing-viewcontrollers/ - Kumar KL
5个回答

16

使用Storyboard、自动布局或不使用,以及一个按钮和一系列子视图控制器进行切换

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

在Storyboard中,您只能将一个嵌入式Segue连接到容器视图。因此,您需要创建一个中间处理控制器。执行嵌入式segue并为其添加标识符,例如EmbededSegueIdentifier。

在父视图控制器中,连接按钮或其他需要的内容,并在prepare 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中,该容器视图控制器仅会执行segue到目标子视图控制器。

- (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,使用从容器视图控制器调用的适当segue标识符前往每个目标。如果不加入一个空的perform方法,应用程序将崩溃。通常,在这里可以进行一些自定义的过渡动画。

@implementation SHCDummySegue

@interface SHCDummySegue : UIStoryboardSegue

@end

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

@end

2
非常好的通用解决方案,不仅适用于替换UITabbarController! - Frank Hartmann

11

缺少免责声明,且只是一个链接回答。 - Daij-Djan

7
更新: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),并实现每个容器来容纳您需要的每个选项卡的任何内容视图(使用容器的隐藏属性)。
这是一个可用不同容器实现的示例: Embed View Controllers 这些容器“交换”当然可以进行动画处理。关于您的自我答案,您可能选择了正确的方法。

据我所知,他没有使用选项卡控制器,这就是为什么容器视图在那里的原因。 - Daij-Djan
@Daij-Djan 我不认为我提到了选项卡栏控制器。我只看到选项卡。关键是,容器视图并不意味着像他想要的那样交换嵌入式视图控制器。 - Roger
我曾考虑过在同一个视图控制器中显示/隐藏所有视图,但作为故事板用户,如果可以将这三个视图保留为单独的视图控制器,那会更方便。这样可以分别设置每个视图控制器的样式并查看其布局(隐藏视图不太方便)。 - Clifton Labrum
@Roger:“容器视图每次都会获得一个新的父级” -> 只有当涉及到TabViewController切换容器时才会发生。这里的想法是切换容器内容。 - Daij-Djan
@CliftonLabrum 不确定是您给我点了踩,但让我澄清一下我的回答,以便更易理解。我还会用一个具体的例子来更新我的回答。 - Roger

2

有一个成员变量来保存视图控制器:

UIViewController *selectedViewController;

现在在IBActions中,切换那个AND视图。例如:

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

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

要触发视图显示并执行一些操作,需要调用removeChildViewController、didMoveToParent、addChildViewController和didMoveToParent等方法。


你能帮我理解 selectedViewController = viewController1; 的作用吗? - Clifton Labrum
我继续遇到让它工作的困难。你能否快速创建一个Xcode项目,在Storyboard中交换UIContainerView的内容,使其在两个不同的viewControllers之间进行切换?我认为我已经正确地连接了它,但是我无法使ControllerView中的视图发生变化。 - Clifton Labrum
这个解决方案可行!只需确保保存所选视图控制器,就像这个例子所示。我跳过了那部分并且一直出现内存错误。那是因为在tab1完成后它被释放了,这阻止了我的新控制器完全工作。 - Fydo

0

我通过使用UITabBarController使其工作。为了使用自定义选项卡,我必须对TabBarController进行子类化,并在代码中将按钮添加到控制器中。然后,我监听按钮上的点击事件并为每个选项卡设置selectedIndex。

这很简单,但对于只有3个选项卡的东西来说,在我的Storyboard中需要很多垃圾。


1
是的,这就是它应该的方式...不过我也经常采用切换视图的方法,就像我概述的那样。 - Daij-Djan
我尝试了手动切换视图的方法,就像你提到的那样,实际上我更喜欢这种方式。这样只有一个导航控制器在运行,一个保存按钮,在我的故事板中减少了很多冗余。我想我最终会采用你提到的方法。我不再看到你的回答了,但感谢你的参与! - Clifton Labrum
我的答案在底部,以“有一个成员变量...”开头 :) - Daij-Djan

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