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


81

我没有找到有关此问题的帖子,但没有一个帖子解决了我的问题。

就像我说的那样。

  1. ViewControllerA
  2. 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不在屏幕上。灰色边框是我为子视图创建的框架。 在此处输入图片说明

Answers:


176

一些观察:

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

    相反,您希望在Interface Builder中为目标视图控制器提供一个“ storyboard id”,然后可以使用instantiateViewController(withIdentifier:)它实例化它(并连接所有IB插座)。在Swift 3中

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

    现在,您可以访问controllerview

  2. 但是,如果您确实想做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视频#102-实现UIViewController包含。总之,你需要确保同步您的视图控制器的层次结构保持与您的视图层次,而这些呼吁addChilddidMove(toParent:)确保这种情况。

    另请参阅《View Controller编程指南》中的创建自定义容器View Controller ”。


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

在此处输入图片说明

这样您就不必担心这些与收容相关的呼叫,Interface Builder会为您解决。

对于Swift 2实现,请参见此答案的先前版本


1
感谢您的详细说明。当我尝试添加ViewControllerB到时ViewControllerAViewControllerB屏幕关闭。我已经用模拟器的屏幕截图编辑了帖子。
Srujan Simha 2014年

有可能 这就是为什么在我的示例中,我frame手动设置了。或者,如果您将其关闭translatesFrameIntoConstraints(或关闭它的名称),也可能可以通过编程方式添加约束。但是,如果要添加子视图,则负责以一种方式或另一种方式设置其框架,就像对所有以编程方式添加的子视图一样。
罗布

1
这是添加边界的方法controller.view.frame = UIScreen.mainScreen().bounds
-Codetard

3
在进行视图控制器包含时,您确实应该引用超级视图,而不是屏幕。坦白说,既然我们已经完成了分屏多任务处理,那么通常不建议做任何引用屏幕的事情。
罗布

1
@Honey-我不确定“假设所有viewController的视图只是parentViewController的一个子视图”的意思。根据定义,当您执行时addSubview,子控制器的根视图是添加了该视图的视图的子视图。您要做的就是在子控制器的根视图和您刚刚将其添加为子视图的视图之间添加约束。
罗布

48

感谢Rob。添加第二次观察的详细语法:

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)

并删除viewcontroller:

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

6
BTW,打电话时addChildViewController,千万不能打电话willMoveToParentViewController。呼叫addChildViewController将为您呼叫。请参阅《适用于iOSView Controller编程指南》中的添加和删​​除子项 由于这个原因,我个人总是会addChildViewController在实例化之后立即对其进行调用,但是在进行配置之前。
罗布

1
当你做controller.ANYPROPERTY = THEVALUE ..我猜在子视图控制器中定义了AnyProperty。我试过了,这给了我一个错误。任何想法如何纠正这一点。
Anuj Arora 2015年

@Anuj Arora你的猜测是正确的。ANYPROPERTY在子viewController中定义。您可以在子viewcontroller中检查ANYPROPERTY,但在ViewDidAppear中不查看。
苏尼塔

7
This code will work for Swift 4.2.

let controller:SecondViewController = 
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)

难道willMoveaddChild为您做到这一点。请参阅willMove 文档。因此顺序为(1)addChild; (2)配置子vc的视图并将其添加到视图层次结构中;和(3)呼叫didMove(toParent:)
Rob

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()
        }
        
    }
}

不要打电话willMove。如willMove 文档所述,该addChild功能可以为您完成。
罗布

3

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()

        })
    }

1

感谢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”对我有用!
Mehdico

不要打电话willMove。如willMove 文档所述,该addChild功能可以为您完成。两次调用都没有好处。
罗布

0

另请参阅有关实现自定义容器视图控制器的官方文档:

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)
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.