2014-12-03 9 views
40

Ich habe wenige Posts für dieses Problem gefunden, aber keine von ihnen hat mein Problem gelöst.Hinzufügen eines View-Controllers als Unteransicht in einem anderen View-Controller

sagen, wie ich habe ..

  1. ViewControllerA
  2. ViewControllerB

Ich versuchte ViewControllerB als Subview in ViewControllerA hinzuzufügen, aber es wie "fatal error: unexpectedly found nil while unwrapping an Optional value" einen Fehler hat zu werfen.

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 mit einem Etikett in es nur ein einfacher Bildschirm.

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

EDIT

Mit der vorgeschlagenen Lösung aus den Benutzer Antworten, ViewControllerB in ViewControllerA wird der Bildschirm abgehend. Der graue Rahmen ist der Rahmen, den ich für die Unteransicht erstellt habe. enter image description here

Antwort

104

Ein paar Beobachtungen:

  1. Wenn Sie die zweite View-Controller instanziiert werden Sie ViewControllerB() aufrufen. Wenn dieser View-Controller seine Ansicht programmatisch erstellt (was ungewöhnlich ist), wäre das in Ordnung. Aber das Vorhandensein der IBOutlet schlägt vor, dass die Szene dieses zweiten Ansicht-Controllers in Interface Builder definiert wurde, aber indem Sie ViewControllerB() aufrufen, geben Sie dem Storyboard nicht die Möglichkeit, diese Szene zu instanziieren und alle Ausgänge zu verbinden. Daher ist das implizit ausgepackte UILabelnil, was zu Ihrer Fehlermeldung führt.

    Stattdessen möchten Sie Ihrem Ziel-View-Controller eine "Storyboard-ID" in Interface Builder geben und dann instantiateViewController(withIdentifier:) verwenden, um ihn zu instanziieren (und alle IB-Outlets anzuschließen). In Swift 3:

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

    können Sie nun Zugriff auf diese controller ‚s view.

  2. Aber wenn Sie wirklich wollen addSubview (d. H. Sie nicht in die nächste Szene wechseln), dann engagieren Sie sich in einer Praxis namens "View Controller Containment". Sie wollen einfach nicht einfach addSubview. Sie wollen einige zusätzliche Container-View-Controller Anrufe tun, z.B .:

    let controller = storyboard.instantiateViewController(withIdentifier: "scene storyboard id") 
    addChildViewController(controller) 
    controller.view.frame = ... // or better, turn off `translatesAutoresizingMaskIntoConstraints` and then define constraints for this subview 
    view.addSubview(controller.view) 
    controller.didMove(toParentViewController: self) 
    

    Weitere Informationen darüber, warum diese addChildViewController und didMove(toParentViewController:) erforderlich sind, finden Sie WWDC 2011 video #102 - Implementing UIViewController Containment.Kurz gesagt, Sie müssen sicherstellen, dass die View-Controller-Hierarchie mit Ihrer Ansichtshierarchie synchron bleibt, und diese Aufrufe an addChildViewController und didMove(toParentViewController:) stellen sicher, dass dies der Fall ist.

    Siehe auch Creating Custom Container View Controllers im View Controller Programming Guide.


By the way, zeigt die oben, wie dies programmatisch zu tun. Es ist viel einfacher, wenn Sie die "Containeransicht" in Interface Builder verwenden.

enter image description here

Dann haben Sie nicht über eine dieser Eindämmungsbezogene Anrufe sorgen, und Interface Builder wird für Sie darum kümmern.

Für die Implementierung von Swift 2 siehe previous revision of this answer.

+1

Vielen Dank für die ausführliche Erklärung. Wenn ich 'ViewControllerB' zu' ViewControllerA' hinzufügte, ging 'ViewControllerB' vom Bildschirm aus. Ich habe meinen Beitrag mit dem Screenshot des Simulators bearbeitet. –

+0

Das ist möglich. Deshalb habe ich in meinem Beispiel den 'frame' manuell gesetzt. Oder wenn Sie 'translaisFrameIntoConstraints' (oder wie auch immer es heißt) ausschalten, und Sie können Constraints wahrscheinlich auch programmatisch hinzufügen. Wenn Sie jedoch die Unteransicht hinzufügen, sind Sie für die Festlegung des Rahmens auf die eine oder andere Weise verantwortlich, genau wie für alle programmatisch hinzugefügten Unteransichten. – Rob

+0

So können Sie Grenzen hinzufügen 'controller.view.frame = UIScreen.mainScreen(). Bounds' –

32

Dank Rob. Hinzufügen Ausführliche Informationen zu Syntax für Ihre zweite Bemerkung:

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) 

Und die Viewcontroller zu entfernen:

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

BTW, beim Aufruf von 'addChildViewController', rufen Sie' willMoveToParentViewController' nicht auf. Der Aufruf von 'addChildViewController' ruft es für Sie auf. Siehe [Hinzufügen und Entfernen eines untergeordneten Elements] (https://developer.apple.com/library/ios/featuredarticles/ViewControllerPGforiPhoneOS/CreatingCustomContainerViewControllers/CreatingCustomContainerViewControllers.html#//apple_ref/doc/uid/TP40007457-CH18-SW13) im _View Controller-Programmierhandbuch für iOS._ Aus diesem Grund würde ich persönlich 'addChildViewController' sofort nach der Instanziierung, aber vor der Konfiguration aufrufen. – Rob

+1

Wenn Sie controller.ANYPROPERTY = THEVALUE..Ich rate, dass AnyProperty in der ChildViewController definiert ist. Ich habe es versucht und es gab mir einen Fehler. Irgendeine Idee, wie das zu korrigieren ist. –

+0

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

0

Bitte auch die offizielle Dokumentation einen benutzerdefinierten Container-View-Controller an der Umsetzung:

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

Diese Dokumentation enthält viel detailliertere Informationen für jede Anweisung und beschreibt auch, wie man Übergänge hinzufügt.

zu Swift 3 Übersetzt:

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

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

     }) 
    } 
Verwandte Themen