Dodawanie kontrolera widoku jako widoku podrzędnego w innym kontrolerze widoku

81

Znalazłem kilka postów dotyczących tego problemu, ale żaden z nich nie rozwiązał mojego problemu.

Powiedz, jakbym ...

  1. ViewControllerA
  2. ViewControllerB

Próbowałem dodać ViewControllerB jako widok podrzędny w ViewControllerA, ale zgłasza błąd, taki jak „ fatal error: unexpectedly found nil while unwrapping an Optional value”.

Poniżej kod ...

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 to zwykły ekran z etykietą.

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

EDYTOWAĆ

Z sugerowanym rozwiązaniem udzielonym przez użytkownika, ViewControllerB w ViewControllerA zniknie z ekranu. Szara ramka to ramka, którą utworzyłem dla widoku podrzędnego. wprowadź opis obrazu tutaj

Srujan Simha
źródło

Odpowiedzi:

178

Kilka uwag:

  1. Podczas tworzenia wystąpienia drugiego kontrolera widoku dzwonisz ViewControllerB(). Jeśli ten kontroler widoku programowo tworzy swój widok (co jest niezwykłe), byłoby dobrze. Ale obecność IBOutletsugeruje, że scena kontrolera drugiego widoku została zdefiniowana w Interface Builder, ale dzwoniąc ViewControllerB(), nie dajesz storyboardowi szansy na utworzenie instancji tej sceny i podłączenie wszystkich gniazd. W ten sposób niejawnie rozpakowany UILabeljest nilkomunikat o błędzie.

    Zamiast tego chcesz nadać kontrolerowi widoku docelowego „identyfikator scenorysu” w programie Interface Builder, a następnie możesz go użyć instantiateViewController(withIdentifier:)do jego utworzenia (i podłączenia wszystkich gniazd IB). W Swift 3:

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

    Teraz można przejść do tego controller„s view.

  2. Ale jeśli naprawdę chcesz to zrobić addSubview(tj. Nie przechodzisz do następnej sceny), wtedy angażujesz się w praktykę zwaną „powstrzymywaniem kontrolera widoku”. Nie chcesz tylko po prostu addSubview. Chcesz wykonać dodatkowe wywołania kontrolera widoku kontenera, np .:

    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)
    

    Aby uzyskać więcej informacji o tym, dlaczego to addChild(wcześniej wywoływane addChildViewController) i didMove(toParent:)(wcześniej wywoływane didMove(toParentViewController:)) są konieczne, zobacz wideo WWDC 2011 nr 102 - Implementowanie UIViewController Containment . Krótko mówiąc, musisz upewnić się, że hierarchia kontrolera widoku pozostaje zsynchronizowana z hierarchią widoku, a te wywołania addChildi didMove(toParent:)upewnij się, że tak jest.

    Zobacz także Tworzenie niestandardowych kontrolerów widoku kontenera w Podręczniku programowania kontrolera widoku.


Nawiasem mówiąc, powyższe ilustruje, jak zrobić to programowo. W rzeczywistości jest to znacznie łatwiejsze, jeśli używasz „widoku kontenera” w programie Interface Builder.

wprowadź opis obrazu tutaj

Wtedy nie musisz się martwić o żadne z tych wywołań związanych z powstrzymywaniem, a Interface Builder zajmie się tym za Ciebie.

W przypadku implementacji Swift 2 zobacz poprzednią wersję tej odpowiedzi .

Obrabować
źródło
1
Dziękuję za szczegółowe wyjaśnienie. Kiedy próbowałem dodając ViewControllerBdo ViewControllerA, ViewControllerBdzieje się na ekranie. Edytowałem swój post ze zrzutem ekranu symulatora.
Srujan Simha
To jest możliwe. Dlatego w moim przykładzie ustawiłem frameręcznie. Lub jeśli wyłączysz translatesFrameIntoConstraints(lub jak to się nazywa), i prawdopodobnie możesz również programowo dodać ograniczenia. Ale jeśli dodajesz widok podrzędny, jesteś odpowiedzialny za ustawienie jego ramki w taki czy inny sposób, tak jak w przypadku wszystkich programowo dodanych widoków podrzędnych.
Rob
1
W ten sposób możesz dodać granicecontroller.view.frame = UIScreen.mainScreen().bounds
Codetard
3
Robiąc zawarcie kontrolera widoku, naprawdę powinieneś odwoływać się do superviewu, a nie ekranu. Szczerze mówiąc, teraz, gdy mamy wielozadaniowość na podzielonym ekranie, robienie wszystkiego, co odnosi się do ekranu, jest generalnie niewskazane.
Rob
1
@ Kochanie - nie jestem pewien, co masz na myśli, mówiąc „załóżmy, że cały widok ViewControllera to tylko jeden podwidok elementu parentViewController”. Z definicji, kiedy to zrobisz addSubview, widok główny kontrolera podrzędnego jest widokiem podrzędnym widoku, do którego go dodałeś. Wszystko, co musisz zrobić, to dodać ograniczenia między widokiem głównym kontrolera podrzędnego a widokiem, do którego właśnie dodałeś go jako widok podrzędny.
Rob
50

Dzięki Rob. Dodanie szczegółowej składni do drugiej obserwacji:

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)

Aby usunąć kontroler widoku:

self.willMoveToParentViewController(nil)
self.view.removeFromSuperview()
self.removeFromParentViewController() 
SML
źródło
6
BTW, dzwoniąc addChildViewController, nie dzwoń willMoveToParentViewController. Dzwonienie addChildViewControllerzadzwoni za Ciebie. Zobacz Dodawanie i usuwanie dziecka w przewodniku programowania kontrolera wyświetlania dla systemu iOS. Z tego powodu osobiście zawsze dzwoniłbym addChildViewControllernatychmiast po jego utworzeniu, ale przed skonfigurowaniem.
Rob
1
Kiedy robisz controller.ANYPROPERTY = THEVALUE..Gaduję, że AnyProperty jest zdefiniowana w childViewController. Próbowałem i dawało mi to błąd. Każdy pomysł, jak to naprawić.
Anuj Arora
@Anuj Arora Twoje przypuszczenie jest słuszne. ANYPROPERTY jest zdefiniowana w podrzędnym kontrolerze widoku. Możesz sprawdzić ANYPROPERTY w podrzędnym kontrolerze widoku, ale w ViewDidAppear nie w 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)
Nirbhay Singh
źródło
Czy nie zadzwonić willMove. addChildrobi to za Ciebie. Zobacz willMove dokumentację . Zatem sekwencja to (1) addChild; (2) skonfiguruj widok podrzędnego vc i dodaj go do hierarchii widoków; i (3) zadzwońdidMove(toParent:)
Rob
4

Aby dodać i usunąć 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()
        }
        
    }
}
krishnan
źródło
Nie dzwoń willMove. Jak mówi willMove dokumentacja , addChildrobi to za Ciebie.
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()

        })
    }
jaya
źródło
2

Dzięki Robowi zaktualizowaliśmy składnię 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)
Emre Gürses
źródło
1
użyj „controller.view.frame = self.view.bounds” zamiast „controller.view.frame = self.view.frame” działa dla mnie!
Sabrina,
Nie dzwoń willMove. Jak mówi willMove dokumentacja , addChildrobi to za Ciebie. Nie ma nic dobrego z dwukrotnego wezwania.
Rob
0

Zapoznaj się również z oficjalną dokumentacją dotyczącą wdrażania niestandardowego kontrolera widoku kontenera:

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

Ta dokumentacja zawiera znacznie bardziej szczegółowe informacje o każdej instrukcji, a także opisuje, jak dodawać przejścia.

Przetłumaczone na 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)
    }
}
Simon Backx
źródło