Co właściwie robi addChildViewController?

102

Po raz pierwszy zanurzam się w programowaniu na iOS i jedną z pierwszych rzeczy, które musiałem zrobić, jest zaimplementowanie niestandardowego kontrolera widoku kontenera - nazwijmy to SideBarViewController- który zamienia, który z kilku możliwych kontrolerów widoku podrzędnego to pokazuje, prawie dokładnie jak standardowy kontroler paska zakładek . (To prawie kontroler paska kart, ale z ukrytym menu bocznym zamiast paska kart.)

Zgodnie z instrukcjami w dokumentacji Apple dzwonię addChildViewControllerza każdym razem, gdy dodam element podrzędny ViewController do mojego kontenera. Mój kod do zamiany bieżącego kontrolera widoku podrzędnego, który SideBarViewControllerwygląda następująco:

- (void)showViewController:(UIViewController *)newViewController {
    UIViewController* oldViewController = [self.childViewControllers 
                                           objectAtIndex:0];
    
    [oldViewController removeFromParentViewController];
    [oldViewController.view removeFromSuperview];
    
    newViewController.view.frame = CGRectMake(
        0, 0, self.view.frame.size.width, self.view.frame.size.height
    );
    [self addChildViewController: newViewController];
    [self.view addSubview: newViewController.view];
}

Potem zacząłem się zastanawiać, co addChildViewControllertu robi, i zdałem sobie sprawę, że nie mam pojęcia. Oprócz umieszczania nowego ViewControllerw .childViewControllerstablicy, wydaje się, że nie ma to żadnego wpływu. Działania i wyjścia z widoku kontrolera podrzędnego do kontrolera podrzędnego, które ustawiłem w scenorysie, nadal działają dobrze, nawet jeśli nigdy nie dzwonię addChildViewController, i nie mogę sobie wyobrazić, na co jeszcze może to wpłynąć.

Rzeczywiście, jeśli przepiszę swój kod, aby nie wywoływał addChildViewController, a zamiast tego wyglądał tak ...

- (void)showViewController:(UIViewController *)newViewController {

    // Get the current child from a member variable of `SideBarViewController`
    UIViewController* oldViewController = currentChildViewController;

    [oldViewController.view removeFromSuperview];

    newViewController.view.frame = CGRectMake(
        0, 0, self.view.frame.size.width, self.view.frame.size.height
    );
    [self.view addSubview: newViewController.view];

    currentChildViewController = newViewController;
}

... moja aplikacja nadal działa doskonale, o ile wiem!

Dokumentacja Apple nie rzuca zbyt wiele światła na to addChildViewController, co robi, ani dlaczego powinniśmy to nazywać. Cały zakres stosownego opisu tego, co robi ta metoda lub dlaczego powinna być stosowana w jej sekcji w opisie UIViewControllerklasy, jest obecnie:

Dodaje podany kontroler widoku jako element podrzędny. ... Ta metoda jest przeznaczona do wywołania tylko przez implementację niestandardowego kontrolera widoku kontenera. Jeśli zmienisz tę metodę, musisz wywołać super w swojej implementacji.

Jest też ten akapit wcześniej na tej samej stronie:

Kontroler widoku kontenera musi skojarzyć kontroler widoku podrzędnego ze sobą przed dodaniem widoku głównego elementu podrzędnego do hierarchii widoków. Dzięki temu system iOS może prawidłowo kierować zdarzenia do kontrolerów widoku podrzędnego i widoków, którymi zarządzają. Podobnie, po usunięciu głównego widoku podrzędnego z hierarchii widoków, powinien odłączyć ten podrzędny kontroler widoku od siebie. Aby utworzyć lub przerwać te skojarzenia, kontener wywołuje określone metody zdefiniowane przez klasę bazową. Te metody nie są przeznaczone do wywoływania przez klientów klasy kontenera; mają być używane tylko przez implementację twojego kontenera, aby zapewnić oczekiwane zachowanie powstrzymujące.

Oto podstawowe metody, z których możesz skorzystać:

addChildViewController:
removeFromParentViewController
willMoveToParentViewController:
didMoveToParentViewController:

ale nie daje żadnej wskazówki co do tego, jakie są „zdarzenia” lub „oczekiwane zachowanie powstrzymujące”, o którym mowa, ani dlaczego (lub nawet kiedy) wywołanie tych metod jest „niezbędne”.

Wszystkie przykłady niestandardowych kontrolerów widoku kontenera w sekcji „Niestandardowe kontrolery widoku kontenera” w dokumentacji Apple wywołują tę metodę, więc zakładam, że służy ona do jakiegoś ważnego celu poza zwykłym umieszczeniem elementu podrzędnego ViewController na tablicy, ale nie mogę zrozumieć co to jest za cel. Co robi ta metoda i dlaczego mam ją nazywać?

Mark Amery
źródło
3
Strona z filmami wideo Apple WWDC 2011 zawiera świetną sesję („Implementing UIViewController Containment”) na ten temat.
Alladinian

Odpowiedzi:

94

Też się zastanawiałem nad tym pytaniem. Obejrzałem sesję 102 filmów z WWDC 2011 i Mr. View Controller, Bruce D. Nilo , powiedział:

viewWillAppear:, viewDidAppear:Itp nie mają nic wspólnego z addChildViewController:. Wystarczy addChildViewController:powiedzieć „Ten kontroler widoku jest dzieckiem tego” i nie ma to nic wspólnego z wyglądem widoku. Kiedy są wywoływane, jest skojarzone z momentami, w których widoki wchodzą i wychodzą z hierarchii okien.

Wydaje się więc, że wezwanie addChildViewController:robi bardzo niewiele. Skutki uboczne rozmowy są ważną częścią. Pochodzą z relacji parentViewControlleri childViewControllers. Oto kilka znanych mi efektów ubocznych:

  • Przekazywanie metod wyglądu do kontrolerów widoku podrzędnego
  • Metody rotacji spedycji
  • (Prawdopodobnie) przekazywanie ostrzeżeń pamięci
  • Unikanie niespójnych hierarchii VC, zwłaszcza gdy transitionFromViewController:toViewController:…oba VC muszą mieć tego samego rodzica
  • Zezwalanie niestandardowym kontrolerom widoku kontenera na udział w zachowaniu i przywracaniu stanu
  • Udział w łańcuchu respondentów
  • Podpinania się navigationController, tabBarControlleritp właściwości
Nevan King
źródło
To sesja 102, a nie 101
SeanChense
+1 dla łańcucha odpowiedzi. addChildViewController jest wymagany, jeśli chcesz otrzymywać zdarzenia dotykowe w podglądzie należącym do dziecka UIViewController
charlieb
108

Myślę, że przykład jest wart tysiąca słów.

Pracowałem nad aplikacją biblioteczną i chciałem pokazać ładny widok notatnika, który pojawia się, gdy użytkownik chce dodać notatkę.

wprowadź opis obrazu tutaj

Po wypróbowaniu kilku rozwiązań ostatecznie wymyśliłem własne niestandardowe rozwiązanie do wyświetlania notatnika. Więc kiedy chcę pokazać notatnik, tworzę nową instancję NotepadViewControlleri dodaję jej widok główny jako widok podrzędny do widoku głównego. Na razie w porządku.

Potem zauważyłem, że obraz notatnika jest częściowo ukryty pod klawiaturą w trybie poziomym.

wprowadź opis obrazu tutaj

Chciałem więc zmienić obraz notatnika i przesunąć go w górę. Aby to zrobić, napisałem odpowiedni kod w willAnimateRotationToInterfaceOrientation:duration:metodzie, ale po uruchomieniu aplikacji nic się nie stało! Po debugowaniu zauważyłem, że żadna z UIViewControllermetod rotacji nie jest w rzeczywistości wywoływana NotepadViewController. Wywoływane są tylko te metody w głównym kontrolerze widoku.

Aby rozwiązać ten problem, musiałem wywołać wszystkie metody NotepadViewControllerręcznie, gdy są wywoływane w kontrolerze widoku głównego. To wkrótce skomplikuje sprawę i stworzy dodatkową zależność między niepowiązanymi komponentami w aplikacji.

To było w przeszłości, zanim wprowadzono koncepcję kontrolerów widoku podrzędnego. Ale teraz musisz tylko przejść addChildViewControllerdo głównego kontrolera widoku i wszystko będzie działać zgodnie z oczekiwaniami bez żadnej dodatkowej pracy ręcznej.

Edycja: istnieją dwie kategorie zdarzeń, które są przekazywane do podrzędnych kontrolerów widoku:

1- Metody wyglądu:

- viewWillAppear:
- viewDidAppear:
- viewWillDisappear:
- viewDidDisappear:

2- Metody rotacji:

- willRotateToInterfaceOrientation:duration:
- willAnimateRotationToInterfaceOrientation:duration:
- didRotateFromInterfaceOrientation:

Możesz także kontrolować, które kategorie zdarzeń mają być automatycznie przekazywane, zastępując shouldAutomaticallyForwardRotationMethodsi shouldAutomaticallyForwardAppearanceMethods.

Hejazi
źródło
Z dokumentacji i po wykonaniu szybkiego testu nie sądzę, aby było jakieś inne zdarzenie, które jest przekazywane tylko wtedy, gdy jesteś addChildViewControllerdo kontrolera nadrzędnego.
Hejazi
życzymy sobie automatycznego przekazywania dalej viewWillLayoutSubviews
MobileMon
10

-[UIViewController addChildViewController:]dodaje tylko przekazany kontroler widoku do tablicy viewControllers, do którego viewController (element nadrzędny) chce zachować odwołanie. W rzeczywistości należy samodzielnie dodać te widoki viewControllera na ekranie, dodając je jako podglądy innego widoku (np. Widok parentViewController). Istnieje również wygodny obiekt w Interface Builder do używania childrenViewControllers w scenorysach.

Wcześniej, aby zachować odniesienie do innych kontrolerów viewControllerów, z których korzystałeś, trzeba było zachować ich ręczne odniesienie w @properties. Posiadanie wbudowanej właściwości, takiej jak childViewControllersiw konsekwencji, parentViewControllerjest wygodnym sposobem zarządzania takimi interakcjami i tworzenia skomponowanych kontrolerów widoku, takich jak UISplitViewController, które można znaleźć w aplikacjach na iPada.

Ponadto childrenViewControllers również automatycznie odbierają wszystkie zdarzenia systemowe, które otrzymuje rodzic: -viewWillAppear, -viewWillDisappear, itp. Wcześniej należało wywoływać te metody ręcznie na swoich "childrenViewControllers".

Otóż ​​to.

Gianluca Tranchedone
źródło
Jakie masz podstawy, by myśleć, że to wszystko? Czy możesz również podać listę „zdarzeń systemowych”, które są odbierane przez dziecko? Wyszukiwanie w Google iOS "system events"nie zwymiotuje; to nie wydaje się być terminem używanym przez Apple?
Mark Amery,
Jest to w zasadzie wygodna metoda, która pozwala na dodanie widoku kontrolera widoku B jako widoku podrzędnego kontrolera widoku A, ale nadal ma kontroler widoku B zarządzający jego widokiem. Aby to działało poprawnie, musisz upewnić się, że kontroler widoku B pobiera zdarzenia systemowe (przeczytaj wywołania zwrotne UIViewControllerDelegate). „addChildViewController” łączy to za Ciebie, aby zaoszczędzić Ci wysiłku związanego z ręcznym przekazywaniem wszystkiego.
Sam Clewlow