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

Ios 将视图控制器添加为另一个视图控制器中的子视图,ios,swift,uiviewcontroller,Ios,Swift,Uiviewcontroller,我找到了一些关于这个问题的帖子,但是没有一个能解决我的问题 说得像我 视图控制器 视图控制器B 我试图将ViewControllerB添加为ViewControllerA中的子视图,但它抛出了一个错误,如“致命错误:在展开可选值时意外发现nil” 下面是代码 ViewControllerA var testVC: ViewControllerB = ViewControllerB(); override func viewDidLoad() { super.viewDidLoad()

我找到了一些关于这个问题的帖子,但是没有一个能解决我的问题

说得像我

  • 视图控制器
  • 视图控制器B
  • 我试图将ViewControllerB添加为ViewControllerA中的子视图,但它抛出了一个错误,如“
    致命错误:在展开可选值时意外发现nil

    下面是代码

    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将退出屏幕。灰色边框是我为子视图创建的框架。
    有几个观察结果:

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

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

    let controller = storyboard!.instantiateViewController(withIdentifier: "scene storyboard id")
    
    您现在可以访问此
    控制器的
    视图

  • 但是,如果您确实想执行
    添加子视图
    (即,您没有过渡到下一个场景),那么您正在进行一种称为“视图控制器包含”的练习。您不仅仅想简单地
    添加子视图
    。您希望执行一些附加的容器视图控制器调用,例如:

    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)
    
    有关为什么需要此操作(以前称为
    addChildViewController
    )和(以前称为
    didMove(toParentViewController:)
    )的更多信息,请参阅。简而言之,您需要确保视图控制器层次结构与视图层次结构保持同步,并且这些对
    addChild
    didMove(toParent:)
    的调用确保了这种情况

    另请参见《视图控制器编程指南》中的


  • 顺便说一句,上面说明了如何通过编程实现这一点。如果在Interface Builder中使用“容器视图”,实际上会容易得多

    这样,您就不必担心任何与容器相关的调用,Interface Builder将为您处理这些调用

    有关Swift 2的实施,请参阅。

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

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

    本文档对每一条指令都有更详细的信息,还描述了如何添加转换

    翻译成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)
        }
    }
    
    func callForMenuView() {


    感谢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)
    

    该代码适用于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)
    

    用于添加和删除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()
            }
            
        }
    }
    

    感谢您的详细解释。当我尝试将
    ViewControllerB
    添加到
    ViewControllerA
    时,
    ViewControllerB
    将退出屏幕。我已经用模拟器的屏幕截图编辑了我的文章。这是可能的。这就是为什么在我的示例中,我手动设置
    。或者如果您关闭
    translatesFrameIntoConstraints
    (或其他名称),并且您可能也可以通过编程方式添加约束。但是,如果您要添加子视图,您需要以某种方式设置其框架,就像您对所有通过编程方式添加的子视图一样。这就是如何添加边界
    controller.view.frame=UIScreen.mainScreen().bounds
    在执行视图控制器包含时,您真的应该引用superview,而不是屏幕。坦率地说,现在我们已经进行了分屏多任务处理,所以执行任何引用屏幕的操作通常都是不可取的。@亲爱的,我不确定您的意思是什么“假设viewController的所有视图都只是parentViewController的一个子视图"。根据定义,当您执行添加子视图时,子控制器的根视图是您将其添加到的视图的子视图。您所做的只是在子控制器的根视图和您刚刚将其作为子视图添加到的视图之间添加约束。顺便说一句,在调用
    添加子视图控制器时,不要调用
    willMoveToParentViewController
    。调用
    addChildViewController
    将为您调用它。请参阅iOS的视图控制器编程指南。出于这个原因,我个人总是在实例化它之后,但在配置它之前,立即调用
    addChildViewController
    。当您执行Controller.ANYPROPERTY=值时..我猜at AnyProperty是在childViewController中定义的。我尝试了它,但它给了我一个错误。你知道如何纠正吗。@Anuj Arora你的猜测是对的。AnyProperty是在childViewController中定义的。你可以在childViewController中检查任何属性,但在ViewDidLoad中不显示。使用“controller.view.frame=self.view.bounds”而不是“controller.view.frame=self.view.frame”对我有效!不要调用
    willMove
    。正如
    willMove
    所说,
    addChild
    为你做这件事。两次调用都没有好处。不要调用
    willMove
    addChild
    为你做这件事。请参见
    willMove
    。因此顺序是(1)
    addChild
    ;(2)配置子vc的视图并将其添加到视图层次结构中;(3)调用
    didMove(toParent:) 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()
            }
            
        }
    }