从父视图控制器访问容器视图控制器(iOS)

211
在iOS6中,我注意到了新的容器视图,但不太确定如何从包含视图访问它的控制器。
场景:

example

我希望能够从承载容器视图的视图控制器中访问警报视图控制器中的标签。 它们之间有一个segue,我能用它吗?

完整的解释在这里,适用于现代容器视图:https://dev59.com/KGAg5IYBdhLWcg3wm7_6#23403979 - Fattie
11个回答

368

是的,您可以使用segue来访问子视图控制器(以及其视图和子视图)。 在Storyboard中使用属性检查器为segue命名一个标识符(例如alertview_embed)。 然后,让包含容器视图的父视图控制器实现像这样的方法:

- (void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
   NSString * segueName = segue.identifier;
   if ([segueName isEqualToString: @"alertview_embed"]) {
       AlertViewController * childViewController = (AlertViewController *) [segue destinationViewController];
       AlertView * alertView = childViewController.view;
       // do something with the AlertView's subviews here...
   }
}

1
我们没有进行Segue转场吗?我在这里漏掉了什么吗...? - Adam Waite
25
是的,在将第二个视图控制器设置为第一个视图控制器的子视图控制器时,会出现嵌入式转场。在此之前,会调用prepareForSegue:方法。您可以利用这个机会向子视图控制器传递数据,或者存储对子视图控制器的引用以供以后使用。另请参见http://developer.apple.com/library/ios/#documentation/uikit/reference/UIStoryboardSegue_Class/Reference/Reference.html。 - Peter E
1
啊,是的,“第二个视图控制器被设置为第一个视图控制器的子视图控制器”,当视图加载时吗?现在这更有意义了,谢谢。我现在不在我的项目中,但稍后会测试。 - Adam Waite
1
准确地说,它是在viewDidLoad之前调用的。当viewDidLoad被执行时,父视图和子视图已经连接,并且父视图中的[self childViewControllers]将返回所有子控制器的数组(请参见下面rdelmar的答案)。 - Peter E
2
我对所提出的解决方案有一个警告:在访问(子)目标视图控制器的视图属性时要非常小心:在某些情况下,这将导致其立即调用viewDidLoad。我建议事先设置任何需要的segue数据,以便viewDidLoad可以安全地触发。 - AlwaysLearning
显示剩余5条评论

57

如果您只有一个子视图控制器,您可以简单地使用 self.childViewControllers.lastObject(否则请使用 objectAtIndex:)。


1
@RaphaelOliveira,并不一定。如果你在一个视图中有多个childControllers,那么这将是首选方法。它可以让你同时协调多个容器。prepareForSegue仅有对其正在操作的单个子控制器实例的引用。 - Fydo
2
@Fydo,在“准备转场”中处理所有多个容器的问题是什么? - Lay González
1
如果(恐怖!)你决定从Storyboard切换或不使用seques等,那么你就必须挖掘代码进行更改等操作。 - Tom Andersen
2
这是我的常规做法,但由于我“太早”访问了childViewControllers,所以现在它会崩溃。 - Mazyod

28

用于Swift编程

你可以像这样编写代码

var containerViewController: ExampleViewController?
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    // you can set this name in 'segue.embed' in storyboard
    if segue.identifier == "checkinPopupIdentifierInStoryBoard" {
        let connectContainerViewController = segue.destinationViewController as ExampleViewController
        containerViewController = connectContainerViewController
    }
}

在 if 语句中,segueName 后面的问号有什么用途? "if segueName?" - the Reverend

19

prepareForSegue方法可行,但它依赖于转场标识符的神奇字符串。也许有更好的办法。

如果您知道要使用的VC类,可以使用计算属性很好地实现此目的:

var camperVan: CamperVanViewController? {
  return childViewControllers.flatMap({ $0 as? CamperVanViewController }).first
  // This works because `flatMap` removes nils
}

这依赖于 childViewControllers。虽然我同意依赖第一个子控制器可能会很容易出错,但是命名你要找的类会让这个看起来更加可靠。


3
在一行代码中,返回第一个子视图控制器是CamperVanViewController的数组元素:return childViewControllers.filter { $0 is CamperVanViewController }.first - Adam Waite
1
我现在使用了 childViewControllers.flatMap({ $0 as? CamperVanViewController }).first,我认为这样更好一些,因为它进行了强制转换并且去除了任何 nil 值。 - SimplGy
如果您想要多次访问该视图控制器,那么这是一个非常好的解决方案。 - Gabriel Goncalves
这是没有希望的 - 没有特定的原因,你可能只有一个特定类。这正是标识符存在的原因。只需遵循标准公式... https://dev59.com/KGAg5IYBdhLWcg3wm7_6#23403979 - Fattie
不要使用过滤器来获取第一个元素,而应该使用first(where:)函数。示例:childViewControllers.first(where: { $0 is CamperVanViewController }) - Alexander

9
使用计算属性的Swift 3更新答案:
var jobSummaryViewController: JobSummaryViewController {
    get {
        let ctrl = childViewControllers.first(where: { $0 is JobSummaryViewController })
        return ctrl as! JobSummaryViewController
    }
}

这只迭代子节点列表直到找到第一个匹配项。

8

self.childViewControllers在需要从父视图控制器中进行控制时更为相关。例如,如果子控制器是一个表视图,并且您想通过访问ChildViewController的实例来强制重新加载它或通过按钮点击或任何其他事件在父视图控制器上更改属性,您可以这样做,而不是通过prepareForSegue。两者在不同方面都有其应用。


2

还有另一种使用Swift中switch语句的方法来判断视图控制器的类型:

override func prepare(for segue: UIStoryboardSegue, sender: Any?)
{
  switch segue.destination
  {
    case let aViewController as AViewController:
      self.aViewController = aViewController
    case let bViewController as BViewController:
      self.bViewController = bViewController
    default:
      return
  }
}

1

使用泛型,你可以做一些很棒的事情。这里是一个针对数组的扩展:

extension Array {
    func firstMatchingType<Type>() -> Type? {
        return first(where: { $0 is Type }) as? Type
    }
}

你可以在你的视图控制器中这样做:

var viewControllerInContainer: YourViewControllerClass? {
    return childViewControllers.firstMatchingType()!
}

1
如果有人在寻找Swift 3.0,那么viewController1、viewController2等将会可用。
let viewController1 : OneViewController!
let viewController2 : TwoViewController!

// Safety handling of optional String
if let identifier: String = segue.identifier {

    switch identifier {

    case "segueName1":
        viewController1 = segue.destination as! OneViewController
        break

    case "segueName2":
        viewController2 = segue.destination as! TwoViewController
        break

    // ... More cases can be inserted here ...

    default:
        // A new segue is added in the storyboard but not yet including in this switch
        print("A case missing for segue identifier: \(identifier)")
        break
    }

} else {
    // Either the segue or the identifier is inaccessible 
    print("WARNING: identifier in segue is not accessible")
}

1
我使用类似以下的代码:

- (IBAction)showCartItems:(id)sender{ 
  ListOfCartItemsViewController *listOfItemsVC=[self.storyboard instantiateViewControllerWithIdentifier:@"ListOfCartItemsViewController"];
  [self addChildViewController:listOfItemsVC];
 }

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