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

问题描述:

我找到了这个问题的几篇文章,但没有一篇解决了我的问题。在另一个视图控制器中添加视图控制器作为子视图

说我好象..

  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" 
} 

EDIT

随着来自用户的答案的建议的解决方案,在ViewControllerB ViewControllerA会在屏幕上。灰色边框是我为子视图创建的框架。 enter image description here

一对夫妇的意见:

  1. 当你实例第二个视图控制器,您呼叫ViewControllerB()。如果该视图控制器以编程方式创建其视图(这是不寻常的),那就没问题了。但是IBOutlet的存在表明这个第二个视图控制器的场景是在Interface Builder中定义的,但是通过调用ViewControllerB(),您不会给故事板一个实例化该场景并挂接所有插座的机会。因此,隐含地解包UILabelnil,导致您的错误消息。

    取而代之,您希望在Interface Builder中为您的目标视图控制器提供“故事板ID”,然后您可以使用instantiateViewController(withIdentifier:)实例化它(并挂接所有IB插座)。在斯威夫特3:

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

    您现在可以访问此controllerview。但如果你真的想要做addSubview(即你没有转换到下一个场景),那么你正在进行一种叫做“视图控制器控制”的练习。你不只是想简单地addSubview。你想要做一些额外的容器视图控制器调用,例如:

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

    有关为什么这个addChildViewControllerdidMove(toParentViewController:)是必要的,看到WWDC 2011 video #102 - Implementing UIViewController Containment更多信息。简而言之,您需要确保您的视图控制器层次结构与您的视图层次结构保持同步,并且这些对addChildViewControllerdidMove(toParentViewController:)的调用确保这种情况。

    另请参阅“视图控制器编程指南”中的Creating Custom Container View Controllers


顺便说一句,上面说明了如何以编程方式做到这一点。如果您在Interface Builder中使用“容器视图”,实际上它会容易得多。

enter image description here

那么你不必担心这些遏制相关的电话,和Interface Builder会照顾它。

对于Swift 2的实现,请参阅previous revision of this answer

+1

感谢您的详细解释。当我尝试将'ViewControllerB'添加到'ViewControllerA'时,'ViewControllerB'正在关闭屏幕。我用模拟器的屏幕截图编辑了我的帖子。 – 2014-12-04 17:01:15

+0

这是可能的。这就是为什么,在我的例子中,我手动设置了“框架”。或者,如果你关闭了'translatesFrameIntoConstraints'(或者其他所谓的),你也可以通过编程来添加约束条件。但是,如果您添加子视图,则需要负责设置其框架,就像您使用所有以编程方式添加的子视图一样。 – Rob 2014-12-04 18:31:58

+0

这是如何添加边界controller.view.frame = UIScreen.mainScreen()。bounds' – 2016-03-03 09:14:53

感谢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; 
controller.willMoveToParentViewController(self) 
self.view.addSubview(controller.view) 
self.addChildViewController(controller) 
controller.didMoveToParentViewController(self) 

,并删除了视图 - 控制:

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

顺便说一句,当调用'addChildViewController'时,不_调用'willMoveToParentViewController'。调用'addChildViewController'会为你调用它。请参阅_View中的[添加和删除子项](https://developer.apple.com/library/ios/featuredarticles/ViewControllerPGforiPhoneOS/CreatingCustomContainerViewControllers/CreatingCustomContainerViewControllers.html#//apple_ref/doc/uid/TP40007457-CH18-SW13)针对iOS的控制器编程指南_因此,我个人总是在实例化之后立即调用'addChildViewController',但在配置它之前。 – Rob 2015-08-17 12:47:45

+1

当你做的controller.ANYPROPERTY = THEVALUE ..我猜AnyProperty是在childViewController中定义的。我试过了,它给了我一个错误。任何想法如何纠正。 – 2015-10-08 08:15:16

+0

@Anuj Arora你的猜测是对的。 ANYPROPERTY在子viewController中定义。您可以在子ViewController中检查ANYPROPERTY,但在ViewDidAppear中不检查ViewDidLoad中的ANYPROPERTY。 – Sunita 2016-05-19 11:17:01

同时请参阅官方文档上实现自定义的容器视图控制器:

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

本文档包含每条指令的更多详细信息还介绍了如何添加转换。

翻译斯威夫特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) 
    } 
} 

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

     }) 
    }