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

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

像我一样说..

  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 将离开屏幕。灰色边框是我为子视图创建的框架。

【问题讨论】:

    标签: ios swift uiviewcontroller


    【解决方案1】:

    几个观察:

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

      相反,您希望在 Interface Builder 中为您的目标视图控制器提供一个“故事板 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 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)
              }
          }
          

          【讨论】:

            猜你喜欢
            • 2012-12-06
            • 2023-03-11
            • 1970-01-01
            • 2020-10-15
            • 1970-01-01
            • 1970-01-01
            • 2017-09-17
            • 2013-06-05
            相关资源
            最近更新 更多