在另一个视图控制器中添加视图控制器作为子视图

Posted

技术标签:

【中文标题】在另一个视图控制器中添加视图控制器作为子视图【英文标题】:Adding a view controller as a subview in another view controller 【发布时间】:2014-12-03 16:17:26 【问题描述】:

我发现很少有关于这个问题的帖子,但没有一个能解决我的问题。

像我一样说..

    ViewControllerA ViewControllerB

我尝试将 ViewControllerB 添加为 ViewControllerA 中的子视图,但它会引发类似“fatal error: unexpectedly found nil while unwrapping an Optional value”的错误。

下面是代码...

ViewControllerA

var testVC: ViewControllerB = ViewControllerB();

override func viewDidLoad()

    super.viewDidLoad()
    self.testVC.view.frame = CGRectMake(0, 0, 350, 450);
    self.view.addSubview(testVC.view);
    // Do any additional setup after loading the view.

ViewControllerB 只是一个带有标签的简单屏幕。

ViewControllerB

 @IBOutlet weak var test: UILabel!

override func viewDidLoad() 
    super.viewDidLoad()
    test.text = "Success" // Throws ERROR here "fatal error: unexpectedly found nil while unwrapping an Optional value"

编辑

根据用户回答中的建议解决方案,ViewControllerA 中的 ViewControllerB 将离开屏幕。灰色边框是我为子视图创建的框架。

【问题讨论】:

【参考方案1】:

几个观察:

    当您实例化第二个视图控制器时,您调用的是ViewControllerB()。如果该视图控制器以编程方式创建其视图(这是不寻常的),那很好。但是IBOutlet 的存在表明第二个视图控制器的场景是在Interface Builder 中定义的,但是通过调用ViewControllerB(),您并没有给故事板一个实例化该场景并连接所有出口的机会。因此,隐式展开的UILabelnil,从而导致您的错误消息。

    相反,您希望在 Interface Builder 中为您的目标视图控制器提供一个“故事板 ID”,然后您可以使用 instantiateViewController(withIdentifier:) 对其进行实例化(并连接所有 IB 出口)。在 Swift 3 中:

    let controller = storyboard!.instantiateViewController(withIdentifier: "scene storyboard id")
    

    您现在可以访问此controllerview

    但如果你真的想做addSubview(即你没有过渡到下一个场景),那么你正在从事一种称为“视图控制器遏制”的做法。您不只是想简单地addSubview。你想做一些额外的容器视图控制器调用,例如:

    let controller = storyboard!.instantiateViewController(withIdentifier: "scene storyboard id")
    addChild(controller)
    controller.view.frame = ...  // or, better, turn off `translatesAutoresizingMaskIntoConstraints` and then define constraints for this subview
    view.addSubview(controller.view)
    controller.didMove(toParent: self)
    

    要详细了解为什么需要此 addChild(以前称为 addChildViewController)和 didMove(toParent:)(以前称为 didMove(toParentViewController:)),请参阅 WWDC 2011 video #102 - Implementing UIViewController Containment。简而言之,您需要确保您的视图控制器层次结构与您的视图层次结构保持同步,并且这些对addChilddidMove(toParent:) 的调用确保了这种情况。

    另请参阅View Controller 编程指南中的Creating Custom Container View Controllers。


顺便说一句,上面说明了如何以编程方式执行此操作。如果您在 Interface Builder 中使用“容器视图”,实际上会容易得多。

那么您不必担心这些与包含相关的调用,Interface Builder 会为您处理。

有关 Swift 2 的实现,请参阅 previous revision of this answer。

【讨论】:

感谢您的详细解释。当我尝试将ViewControllerB 添加到ViewControllerA 时,ViewControllerB 正在离开屏幕。我已经用模拟器的截图编辑了我的帖子。 这是可能的。这就是为什么在我的示例中,我手动设置了frame。或者,如果您关闭 translatesFrameIntoConstraints(或其他任何名称),您也可以通过编程方式添加约束。但是,如果您要添加子视图,则需要以一种或另一种方式设置其框架,就像您为所有以编程方式添加的子视图一样。 这是添加边界的方法controller.view.frame = UIScreen.mainScreen().bounds 在进行视图控制器包含时,您确实应该引用超级视图,而不是屏幕。坦率地说,现在我们有了分屏多任务处理,做任何涉及屏幕的事情通常都是不可取的。 @Honey - 我不确定您所说的“假设所有 viewController 的视图只是 parentViewController 的一个子视图”是什么意思。根据定义,当您执行addSubview 时,子控制器的根视图是您添加它的视图的子视图。您所做的只是在子控制器的根视图和您刚刚将其作为子视图添加到的视图之间添加约束。【参考方案2】:

感谢罗伯。 为您的第二次观察添加详细语法:

let controller:MyView = self.storyboard!.instantiateViewControllerWithIdentifier("MyView") as! MyView
controller.ANYPROPERTY=THEVALUE // If you want to pass value
controller.view.frame = self.view.bounds
self.view.addSubview(controller.view)
self.addChildViewController(controller)
controller.didMoveToParentViewController(self)

并删除视图控制器:

self.willMoveToParentViewController(nil)
self.view.removeFromSuperview()
self.removeFromParentViewController() 

【讨论】:

当你做 controller.ANYPROPERTY = THEVALUE ..我猜 AnyProperty 是在 childViewController 中定义的。我试过了,它给了我一个错误。知道如何纠正这个问题。 @Anuj Arora 你的猜测是对的。 ANYPROPERTY 在子视图控制器中定义。您可以在子视图控制器中检查 ANYPROPERTY,但在 ViewDidAppear 中而不是在 ViewDidLoad 中。【参考方案3】:

此代码适用于 Swift 4.2。

let controller = self.storyboard!.instantiateViewController(withIdentifier: "secondViewController") as! SecondViewController
controller.view.frame = self.view.bounds
self.view.addSubview(controller.view)
self.addChild(controller)
controller.didMove(toParent: self)

【讨论】:

【参考方案4】:

用于添加和删除 ViewController

 var secondViewController :SecondViewController?

  // Adding 
 func add_ViewController() 
    let controller  = self.storyboard?.instantiateViewController(withIdentifier: "secondViewController")as! SecondViewController
    controller.view.frame = self.view.bounds
    self.view.addSubview(controller.view)
    self.addChild(controller)
    controller.didMove(toParent: self)
    self.secondViewController = controller


// Removing
func remove_ViewController(secondViewController:SecondViewController?) 
    if secondViewController != nil 
        if self.view.subviews.contains(secondViewController!.view) 
             secondViewController!.view.removeFromSuperview()
        
        
    

【讨论】:

【参考方案5】:

感谢 Rob,更新了 Swift 4.2 语法

let controller:WalletView = self.storyboard!.instantiateViewController(withIdentifier: "MyView") as! WalletView
controller.view.frame = self.view.bounds
self.view.addSubview(controller.view)
self.addChild(controller)
controller.didMove(toParent: self)

【讨论】:

使用“controller.view.frame = self.view.bounds”而不是“controller.view.frame = self.view.frame”对我有用!【参考方案6】:

func callForMenuView()

    if(!isOpen)

    
        isOpen = true

        let menuVC : MenuViewController = self.storyboard!.instantiateViewController(withIdentifier: "menu") as! MenuViewController
        self.view.addSubview(menuVC.view)
        self.addChildViewController(menuVC)
        menuVC.view.layoutIfNeeded()

        menuVC.view.frame=CGRect(x: 0 - UIScreen.main.bounds.size.width, y: 0, width: UIScreen.main.bounds.size.width-90, height: UIScreen.main.bounds.size.height);

        UIView.animate(withDuration: 0.3, animations:  () -> Void in
            menuVC.view.frame=CGRect(x: 0, y: 0, width: UIScreen.main.bounds.size.width-90, height: UIScreen.main.bounds.size.height);
    , completion:nil)

    else if(isOpen)
    
        isOpen = false
      let viewMenuBack : UIView = view.subviews.last!

        UIView.animate(withDuration: 0.3, animations:  () -> Void in
            var frameMenu : CGRect = viewMenuBack.frame
            frameMenu.origin.x = -1 * UIScreen.main.bounds.size.width
            viewMenuBack.frame = frameMenu
            viewMenuBack.layoutIfNeeded()
            viewMenuBack.backgroundColor = UIColor.clear
        , completion:  (finished) -> Void in
            viewMenuBack.removeFromSuperview()

        )
    

【讨论】:

【参考方案7】:

还请查看有关实现自定义容器视图控制器的官方文档:

https://developer.apple.com/library/content/featuredarticles/ViewControllerPGforiPhoneOS/ImplementingaContainerViewController.html#//apple_ref/doc/uid/TP40007457-CH11-SW1

本文档为每条指令提供了更详细的信息,还描述了如何添加过渡。

翻译成 Swift 3:

func cycleFromViewController(oldVC: UIViewController,
               newVC: UIViewController) 
   // Prepare the two view controllers for the change.
   oldVC.willMove(toParentViewController: nil)
   addChildViewController(newVC)

   // Get the start frame of the new view controller and the end frame
   // for the old view controller. Both rectangles are offscreen.r
   newVC.view.frame = view.frame.offsetBy(dx: view.frame.width, dy: 0)
   let endFrame = view.frame.offsetBy(dx: -view.frame.width, dy: 0)

   // Queue up the transition animation.
   self.transition(from: oldVC, to: newVC, duration: 0.25, animations:  
        newVC.view.frame = oldVC.view.frame
        oldVC.view.frame = endFrame
    )  (_: Bool) in
        oldVC.removeFromParentViewController()
        newVC.didMove(toParentViewController: self)
    

【讨论】:

以上是关于在另一个视图控制器中添加视图控制器作为子视图的主要内容,如果未能解决你的问题,请参考以下文章

UITableViewController 作为小视图中的子视图控制器 - 键盘问题

从父视图控制器再次推送时,子视图框架发生变化

添加全屏视图作为视图控制器的子视图

在 ios 中添加视图控制器作为子视图

我应该将底部工作表作为子视图添加到当前视图控制器还是推送添加了子视图的 UIWindow?

添加根视图控制器 OCMockObject[UIViewController] 作为子视图控制器错误