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 ..
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 .
Quelques observations:
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
.
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.
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 .
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()
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)
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)
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()
})
}
Veuillez également consulter la documentation officielle sur la mise en œuvre d'un contrôleur de vue de conteneur personnalisé:
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)
}
}
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()
}
}
}