Hinzufügen eines Ansichtscontrollers als Unteransicht in einem anderen Ansichtscontroller


81

Ich habe nur wenige Beiträge für dieses Problem gefunden, aber keiner von ihnen hat mein Problem gelöst.

Sag wie ich ..

  1. ViewControllerA
  2. ViewControllerB

Ich habe versucht, ViewControllerB als Unteransicht in ViewControllerA hinzuzufügen, aber es wird ein Fehler wie " fatal error: unexpectedly found nil while unwrapping an Optional value" ausgegeben .

Unten ist der 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 ist nur ein einfacher Bildschirm mit einer Beschriftung.

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

BEARBEITEN

Mit der vorgeschlagenen Lösung aus den Benutzerantworten verlässt ViewControllerB in ViewControllerA den Bildschirm. Der graue Rand ist der Rahmen, den ich für die Unteransicht erstellt habe. Geben Sie hier die Bildbeschreibung ein

Antworten:


177

Einige Beobachtungen:

  1. Wenn Sie den zweiten Ansichts-Controller instanziieren, rufen Sie an ViewControllerB(). Wenn dieser Ansichts-Controller programmgesteuert seine Ansicht erstellt (was ungewöhnlich ist), wäre dies in Ordnung. Das Vorhandensein von IBOutletdeutet jedoch darauf hin, dass die Szene dieses zweiten Ansichtscontrollers in Interface Builder definiert wurde. Wenn Sie jedoch aufrufen ViewControllerB(), geben Sie dem Storyboard nicht die Möglichkeit, diese Szene zu instanziieren und alle Steckdosen anzuschließen. So sind die implizit ungeöffneten UILabelheißt nil, was in der Fehlermeldung.

    Stattdessen möchten Sie Ihrem Zielansichts-Controller im Interface Builder eine "Storyboard-ID" zuweisen und diese dann instantiateViewController(withIdentifier:)instanziieren (und alle IB-Ausgänge anschließen). In Swift 3:

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

    Sie können nun dieser Zugriff controllerist view.

  2. Wenn Sie dies jedoch wirklich tun möchten addSubview(dh nicht zur nächsten Szene wechseln), üben Sie eine Übung namens "View Controller Containment" aus. Sie wollen nicht nur einfach addSubview. Sie möchten einige zusätzliche Aufrufe des Container View Controllers ausführen, z.

    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)
    

    Weitere Informationen dazu, warum dies addChild(zuvor aufgerufen addChildViewController) und didMove(toParent:)(zuvor aufgerufen didMove(toParentViewController:)) erforderlich sind, finden Sie in WWDC 2011-Video Nr. 102 - Implementieren von UIViewController Containment . Kurz gesagt, Sie müssen sicherstellen, dass Ihre View Controller-Hierarchie mit Ihrer View-Hierarchie synchron bleibt. Diese Aufrufe rufen dies auf addChildund stellen didMove(toParent:)sicher, dass dies der Fall ist.

    Siehe auch Erstellen benutzerdefinierter Container-View-Controller im View Controller-Programmierhandbuch.


Das obige Beispiel zeigt übrigens, wie dies programmgesteuert durchgeführt wird. Es ist tatsächlich viel einfacher, wenn Sie die "Containeransicht" in Interface Builder verwenden.

Geben Sie hier die Bildbeschreibung ein

Dann müssen Sie sich um keinen dieser Containment-bezogenen Aufrufe kümmern, und Interface Builder kümmert sich um Sie.

Informationen zur Implementierung von Swift 2 finden Sie in der vorherigen Version dieser Antwort .


1
Vielen Dank für die ausführliche Erklärung. Als ich versuchte , das Hinzufügen ViewControllerBzu ViewControllerA, ViewControllerBwird der Bildschirm abgehend. Ich habe meinen Beitrag mit dem Screenshot des Simulators bearbeitet.
Srujan Simha

Das ist möglich. Deshalb habe ich in meinem Beispiel das framemanuell eingestellt. Oder wenn Sie ausschalten translatesFrameIntoConstraints(oder wie auch immer es heißt) und Sie wahrscheinlich auch programmgesteuert Einschränkungen hinzufügen können. Wenn Sie jedoch die Unteransicht hinzufügen, müssen Sie den Rahmen auf die eine oder andere Weise festlegen, genau wie bei allen programmgesteuert hinzugefügten Unteransichten.
Rob

1
So können Sie Grenzen hinzufügencontroller.view.frame = UIScreen.mainScreen().bounds
Codetard

3
Wenn Sie View Controller enthalten, sollten Sie wirklich auf die Übersicht verweisen, nicht auf den Bildschirm. Ehrlich gesagt, ist es nach dem Split-Screen-Multitasking im Allgemeinen nicht ratsam, alles zu tun, was auf den Bildschirm verweist.
Rob

1
@Honey - Ich bin mir nicht sicher, was Sie unter "Angenommen, die Ansicht von viewController ist nur eine Unteransicht des parentViewController" verstehen. Wenn Sie dies tun addSubview, ist die Stammansicht des untergeordneten Controllers per Definition eine Unteransicht der Ansicht, zu der Sie sie hinzugefügt haben. Sie müssen lediglich Einschränkungen zwischen der Stammansicht des untergeordneten Controllers und der Ansicht hinzufügen, zu der Sie sie gerade als Unteransicht hinzugefügt haben.
Rob

50

Danke an Rob. Hinzufügen einer detaillierten Syntax für Ihre zweite Beobachtung:

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)

Und um den Viewcontroller zu entfernen:

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

6
BTW, wenn Sie anrufen addChildViewController, Sie nicht nennen willMoveToParentViewController. Wenn Sie addChildViewControlleranrufen, wird es für Sie angerufen. Siehe Hinzufügen und Entfernen eines untergeordneten Elements im View Controller-Programmierhandbuch für iOS. Aus diesem Grund würde ich persönlich immer addChildViewControllersofort nach dem Instanziieren anrufen , aber bevor ich es konfiguriere.
Rob

1
Wenn Sie controller.ANYPROPERTY = THEVALUE....Ich vermute, dass AnyProperty im childViewController definiert ist. Ich habe es versucht und es gab mir einen Fehler. Irgendeine Idee, wie man das korrigiert.
Anuj Arora

@ Anuj Arora Ihre Vermutung ist richtig. ANYPROPERTY ist in Child ViewController definiert. Sie können ANYPROPERTY in Child Viewcontroller überprüfen, in ViewDidAppear jedoch nicht in ViewDidLoad.
Sunita

7
This code will work for Swift 4.2.

let controller:SecondViewController = 
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)

Rufen Sie nicht anwillMove . addChildmacht das für dich Siehe die willMove Dokumentation . Die Sequenz ist also (1) addChild; (2) Konfigurieren Sie die Ansicht des untergeordneten VC und fügen Sie sie der Ansichtshierarchie hinzu. und (3) AnrufdidMove(toParent:)
Rob

4

Zum Hinzufügen und Entfernen von 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()
        }
        
    }
}

Rufen Sie nicht an willMove. Wie die willMove Dokumentation sagt, addChilderledigt das das für Sie.
Rob

3

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

Dank Rob wurde die Swift 4.2-Syntax aktualisiert

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)

Verwenden Sie "controller.view.frame = self.view.bounds" anstelle von "controller.view.frame = self.view.frame" funktioniert für mich!
Sabrina

Rufen Sie nicht an willMove. Wie die willMove Dokumentation sagt, addChilderledigt das das für Sie. Es nützt nichts, wenn man es zweimal nennt.
Rob

0

Bitte lesen Sie auch die offizielle Dokumentation zur Implementierung eines benutzerdefinierten Containersicht-Controllers:

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

Diese Dokumentation enthält viel detailliertere Informationen zu jeder Anweisung und beschreibt auch, wie Übergänge hinzugefügt werden.

Übersetzt nach 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)
    }
}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.