web-dev-qa-db-fra.com

Ajout d'un contrôleur de vue en tant que sous-vue dans un autre contrôleur de vue

J'ai trouvé peu de messages pour ce problème mais aucun d'eux n'a résolu mon problème.

Dis comme si j'avais ..

  1. ViewControllerA
  2. ViewControllerB

J'ai essayé d'ajouter ViewControllerB en tant que sous-vue dans ViewControllerA mais une erreur du type "fatal error: unexpectedly found nil while unwrapping an Optional value" a été générée.

Voici le code ...

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 n’est qu’un simple écran avec une étiquette.

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

MODIFIER

Avec la solution suggérée dans les réponses des utilisateurs, ViewControllerB dans ViewControllerA disparaît de l'écran. La bordure grise est le cadre que j'ai créé pour la sous-vue .enter image description here

54
Srujan Simha

Quelques observations:

  1. Lorsque vous instanciez le second contrôleur de vue, vous appelez ViewControllerB(). Si ce contrôleur de vue crée sa vue par programme (ce qui est inhabituel), tout ira bien. Mais la présence de IBOutlet suggère que la scène de ce second contrôleur de vue a été définie dans Interface Builder, mais en appelant ViewControllerB(), vous ne donnez pas au scénario une occasion d'instancier cette scène et de brancher tous les points de vente. Ainsi, la UILabel implicitement déballée est nil, ce qui entraîne votre message d'erreur.

    Au lieu de cela, vous souhaitez attribuer à votre contrôleur de vue de destination un "id de storyboard" dans Interface Builder, puis vous pouvez utiliser instantiateViewController(withIdentifier:) pour l'instancier (et connecter toutes les prises IB). Dans Swift 3:

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

    Vous pouvez maintenant accéder à cette controller 's view.

  2. Mais si vous voulez vraiment faire addSubview (c’est-à-dire que vous ne passez pas à la scène suivante), vous vous engagez dans une pratique appelée "confinement du contrôleur de vue". Vous ne voulez pas simplement simplement addSubview. Vous souhaitez effectuer des appels supplémentaires de contrôleur de vue conteneur, par exemple

    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)
    

    Pour plus d'informations sur les raisons pour lesquelles ceci addChild (précédemment appelé addChildViewController) et didMove(toParent:) (précédemment appelé didMove(toParentViewController:)) sont nécessaires, voir Vidéo WWDC 2011 n ° 102 - Implémentation de UIViewController Containment . En bref, vous devez vous assurer que la hiérarchie de votre contrôleur de vue reste en phase avec votre hiérarchie de vue, et ces appels à addChild et didMove(toParent:) vous garantissent que c'est bien le cas.

    Voir également Création de contrôleurs de vue de conteneur personnalisés dans le Guide de programmation de View Controller.


À propos, ce qui précède illustre comment procéder par programme. C’est beaucoup plus facile si vous utilisez la "vue conteneur" dans Interface Builder. 

enter image description here

Ensuite, vous n'avez pas à vous soucier de ces appels liés au confinement et Interface Builder s'en chargera pour vous.

Pour l'implémentation de Swift 2, voir révision précédente de cette réponse .

134
Rob

Merci à Rob . Ajout d’une syntaxe détaillée pour votre seconde observation: 

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)

Et pour supprimer le viewcontroller:

self.willMoveToParentViewController(nil)
self.view.removeFromSuperview()
self.removeFromParentViewController() 
37
SML
This code will work for Swift 4.2.

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

Grâce à Rob, syntaxe Swift 4.2 mise à jour

let controller:WalletView = self.storyboard!.instantiateViewController(withIdentifier: "MyView") as! WalletView
            controller.view.frame = self.view.bounds;
            controller.willMove(toParent: self)
            self.view.addSubview(controller.view)
            self.addChild(controller)
            controller.didMove(toParent: self)
1
Emre Gürses

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
jaya

Veuillez également consulter la documentation officielle sur la mise en œuvre d'un contrôleur de vue de conteneur personnalisé:

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

Cette documentation contient des informations beaucoup plus détaillées pour chaque instruction et explique également comment ajouter des transitions.

Traduit en 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)
    }
}
0
Simon Backx

Pour ajouter et supprimer ViewController

 var secondViewController :SecondViewController?

  // Adding 
 func add_ViewController() {
    let controller  = self.storyboard?.instantiateViewController(withIdentifier: "secondViewController")as! SecondViewController
    controller.view.frame = self.view.bounds;
    controller.willMove(toParent: self)
    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()
        }

    }
}
0
krishnan