Jak korzystać z automatycznego układu z przejściami kontenerów?

Jak można użyć Auto Layout z metodą przejścia kontenera UIViewController:

-(void)transitionFromViewController:(UIViewController *)fromViewController
                   toViewController:(UIViewController *)toViewController 
                           duration:(NSTimeInterval)duration
                            options:(UIViewAnimationOptions)options
                         animations:(void (^)(void))animations
                         completion:(void (^)(BOOL finished))completion;

Tradycyjnie, używając sprężyn / rozpórek, ustawiasz początkowe klatki (tuż przed wywołaniem tej metody) i ustawiasz końcowe klatki w bloku animacji, który PRZEKAZUJESZ do metody.

Ta metoda umożliwia dodawanie widoku do hierarchii widoku i uruchamianie animacji za Ciebie.

Problem polega na tym, że nie możemy dodać początkowych ograniczeń w tym samym miejscu (przed wywołaniem metody), ponieważ widok nie został jeszcze dodany do hierarchii widoku.

Jakieś pomysły, Jak mogę użyć tej metody wraz z Auto Layout?

Poniżej znajduje się przykład (Dziękuję) zrobienia tego przy użyciu sprężyn/rozpórek (ramek) http://www.cocoanetics.com/2012/04/containing-viewcontrollers

- (void)transitionFromViewController:(UIViewController *)fromViewController toViewController:(UIViewController *)toViewController
{

    // XXX We can't add constraints here because the view is not yet in the view hierarchy
    // animation setup 
    toViewController.view.frame = _containerView.bounds;
    toViewController.view.autoresizingMask = _containerView.autoresizingMask;

    // notify
    [fromViewController willMoveToParentViewController:nil];
    [self addChildViewController:toViewController];

    // transition
    [self transitionFromViewController:fromViewController
                      toViewController:toViewController
                              duration:1.0
                               options:UIViewAnimationOptionTransitionCurlDown
                            animations:^{
                            }
                            completion:^(BOOL finished) {
                                [toViewController didMoveToParentViewController:self];
                                [fromViewController removeFromParentViewController];
                            }];
}
Author: Mike Vosseller, 2014-01-02

4 answers

Zaczynam myśleć o metodzie użyteczności transitionFromViewController: toViewController: czas trwania: opcje: animacje: Ukończenie nie może być wykonane tak, aby działało czysto z automatycznym układem.

Na razie zastąpiłem użycie tej metody wywołaniami bezpośrednio do każdej z metod przechowawczych "niższego poziomu". Jest to nieco więcej kodu, ale wydaje się, że daje większą kontrolę.

Wygląda tak:

- (void) performTransitionFromViewController:(UIViewController*)fromVc toViewController:(UIViewController*)toVc {

    [fromVc willMoveToParentViewController:nil];
    [self addChildViewController:toVc];

    UIView *toView = toVc.view;
    UIView *fromView = fromVc.view;

    [self.containerView addSubview:toView];

    // TODO: set initial layout constraints here

    [self.containerView layoutIfNeeded];

    [UIView animateWithDuration:.25
                          delay:0
                        options:0
                     animations:^{

                         // TODO: set final layout constraints here

                         [self.containerView layoutIfNeeded];

                     } completion:^(BOOL finished) {
                         [toVc didMoveToParentViewController:self];
                         [fromView removeFromSuperview];
                         [fromVc removeFromParentViewController];
                     }];
}
 11
Author: Mike Vosseller,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-01-03 01:09:11

Prawdziwym rozwiązaniem wydaje się ustawienie ograniczeń w bloku animacji transitionFromViewController:toViewController:duration:options:animations:.

[self transitionFromViewController:fromViewController
                  toViewController:toViewController
                          duration:1.0
                           options:UIViewAnimationOptionTransitionCurlDown
                        animations:^{
                            // SET UP CONSTRAINTS HERE
                        }
                        completion:^(BOOL finished) {
                            [toViewController didMoveToParentViewController:self];
                            [fromViewController removeFromParentViewController];
                        }];
 9
Author: Steve Waddicor,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-07-28 18:36:32

Istnieją dwa rozwiązania w zależności od tego, czy po prostu trzeba ustawić widok za pomocą układu automatycznego (easy), czy też trzeba animować zmiany ograniczeń układu automatycznego (harder).

TL; wersja DR

Jeśli potrzebujesz tylko pozycjonować widok poprzez układ automatyczny, możesz użyć metody -[UIViewController transitionFromViewController:toViewController:duration:options:animations:completion:] i zainstalować ograniczenia w bloku animacji.

Aby animować zmiany ograniczenia układu automatycznego, należy użyć ogólnego wywołania +[UIView animateWithDuration:delay:options:animations:completion:] i dodać kontroler potomny regularnie.

Rozwiązanie 1: Pozycjonowanie widoku przez układ Automatyczny

Zajmijmy się pierwszą, łatwą sprawą. W tym scenariuszu widok powinien być ustawiony za pomocą układu automatycznego, aby zmiana wysokości paska stanu (np. poprzez wybranie Toggle in-Call Status Bar), między innymi, nie zepchnęła rzeczy z ekranu.

Dla odniesienia, otooficjalny kod Apple dotyczący przejścia z jednego kontrolera widoku do inny:

- (void) cycleFromViewController: (UIViewController*) oldC
            toViewController: (UIViewController*) newC
{
    [oldC willMoveToParentViewController:nil];                        // 1
    [self addChildViewController:newC];

    newC.view.frame = [self newViewStartFrame];                       // 2
    CGRect endFrame = [self oldViewEndFrame];

    [self transitionFromViewController: oldC toViewController: newC   // 3
          duration: 0.25 options:0
          animations:^{
             newC.view.frame = oldC.view.frame;                       // 4
             oldC.view.frame = endFrame;
           }
           completion:^(BOOL finished) {
             [oldC removeFromParentViewController];                   // 5
             [newC didMoveToParentViewController:self];
            }];
}

Zamiast używać ramek jak w powyższym przykładzie, musimy dodać ograniczenia. Pytanie tylko, gdzie je dodać. Nie możemy ich dodać w marker (2) powyżej, ponieważ {[7] } nie jest zainstalowana w hierarchii widoku. Jest on instalowany dopiero w momencie wywołania transitionFromViewController... (3). Oznacza to, że możemy albo zainstalować ograniczenia zaraz po wywołaniu transitionFromViewController, albo możemy to zrobić jako pierwsza linia w bloku animacji. Oba powinny zadziałać. Jeśli chcesz to zrobić najwcześniej czas, a następnie umieszczenie go w bloku animacji jest droga do zrobienia. Więcej na temat kolejności wywoływania tych bloków zostanie omówione poniżej.

W podsumowaniu, do pozycjonowania za pomocą auto layoutu, użyj szablonu, takiego jak:

- (void)cycleFromViewController:(UIViewController *)oldViewController
               toViewController:(UIViewController *)newViewController
{
    [oldViewController willMoveToParentViewController:nil];
    [self addChildViewController:newViewController];

    newViewController.view.alpha = 0;

    [self transitionFromViewController:oldViewController
                      toViewController:newViewController
                              duration:0.25
                               options:0
                            animations:^{
                                newViewController.view.translatesAutoresizingMaskIntoConstraints = NO;
                                // create constraints for newViewController.view here

                                newViewController.view.alpha = 1;
                            }
                            completion:^(BOOL finished) {
                                [oldViewController removeFromParentViewController];
                                [newViewController didMoveToParentViewController:self];
                            }];
    // or create constraints right here
}

Rozwiązanie 2: animowanie zmian ograniczeń

Animowanie zmian ograniczeń nie jest tak proste, ponieważ nie otrzymujemy wywołania zwrotnego między momentem dołączenia widoku do hierarchii a momentem wywołania bloku animacji za pomocą transitionFromViewController... metoda.

Dla odniesienia, tutaj jest standardowy sposób dodawania / usuwania kontrolera widoku potomnego:

- (void) displayContentController: (UIViewController*) content;
{
   [self addChildViewController:content];                 // 1
   content.view.frame = [self frameForContentController]; // 2
   [self.view addSubview:self.currentClientView];
   [content didMoveToParentViewController:self];          // 3
}

- (void) hideContentController: (UIViewController*) content
{
   [content willMoveToParentViewController:nil];  // 1
   [content.view removeFromSuperview];            // 2
   [content removeFromParentViewController];      // 3
}

Porównując te dwie metody i oryginalny cycleFromViewController: opublikowany powyżej, widzimy, że transitionFromViewController dba o dwie rzeczy dla nas:

  • [self.view addSubview:self.currentClientView];
  • [content.view removeFromSuperview];

Poprzez dodanie logowania (pominiętego w tym poście), możemy dowiedzieć się, kiedy te metody są wywoływane.

Po zrobieniu tego, wydaje się, że metoda jest zaimplementowana w sposób podobny do następującego:

- (void)transitionFromViewController:(UIViewController *)fromViewController toViewController:(UIViewController *)toViewController duration:(NSTimeInterval)duration options:(UIViewAnimationOptions)options animations:(void (^)(void))animations completion:(void (^)(BOOL finished))completion
{
    [self.view addSubview:toViewController.view];  // A
    animations();                                  // B

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(duration * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [fromViewController.view removeFromSuperview];
        completion(YES);
    });
}

Teraz jest jasne, dlaczego nie można użyć transitionFromViewController do animowania zmian ograniczeń. Pierwszy raz można zainicjować ograniczenia po dodaniu widoku (linia A). Ograniczenia powinny być animowane w bloku animations() (Linia B), ale nie ma możliwości uruchomienia kodu między tymi dwoma liniami.

Dlatego musimy użyć ręcznego bloku animacji, wzdłuż z standardową metodą animowania zmian ograniczeń :

- (void)cycleFromViewController:(UIViewController *)oldViewController
               toViewController:(UIViewController *)newViewController
{
    [oldViewController willMoveToParentViewController:nil];
    [self addChildViewController:newViewController];

    [self.view addSubview:newViewController.view];
    newViewController.view.translatesAutoresizingMaskIntoConstraints = NO;
    // TODO: create initial constraints for newViewController.view here

    [newViewController.view layoutIfNeeded];

    // TODO: update constraint constants here

    [UIView animateWithDuration:0.25
                     animations:^{
                         [newViewController.view layoutIfNeeded];
                     }
                     completion:^(BOOL finished) {
                         [oldViewController.view removeFromSuperview];
                         [oldViewController removeFromParentViewController];
                         [newViewController didMoveToParentViewController:self];
                     }];
}

Ostrzeżenia

Nie jest to równoważne z tym, jak storyboard osadza kontroler widoku kontenera. Na przykład, jeśli porównasz wartość translatesAutoresizingMaskIntoConstraints osadzonego widoku za pomocą storyboardu z powyższą metodą, raportuje YES dla storyboardu i NO (oczywiście, ponieważ wyraźnie ustawiliśmy ją na NO) dla metody, którą polecam powyżej.

Może to prowadzić do niespójności w aplikacji, ponieważ niektóre części systemu zdają się zależeć od przechowalni UIViewController używanej z translatesAutoresizingMaskIntoConstraints ustawioną na NO. Na przykład na iPadzie Air (8.4) może pojawić się dziwne zachowanie podczas obracania z pionowego do poziomego.

Proste rozwiązanie wydaje się być, aby translatesAutoresizingMaskIntoConstraints ustawić na NO, a następnie ustawić newViewController.view.frame = newViewController.view.superview.bounds. Jednak, jeśli nie jesteś bardzo ostrożny z tym, kiedy ta metoda jest wywoływana, najprawdopodobniej da ci nieprawidłowy układ wizualny. (Uwaga: sposób, w jaki storyboard zapewnia rozmiar widoku jest prawidłowy przez ustawienie właściwości autoresize osadzonego widoku na W+H. Wydrukowanie ramki zaraz po dodaniu subview ujawni również różnicę między storyboard a podejściem programatycznym, co sugeruje, że Apple ustawia ramkę bezpośrednio na zamkniętym widoku.)

 8
Author: Senseful,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:34:15

Mam nadzieję, że twoje pytanie zyska na popularności, ponieważ myślę, że jest dobre. Nie mam dla Ciebie ostatecznej odpowiedzi, ale mogę opisać moje własne doświadczenia z sytuacjami podobnymi do Twoich.

Oto wniosek, który wyciągnąłem z moich doświadczeń: nie można używać układu auto bezpośrednio w widoku głównym kontrolera widoku. Jak tylko ustawię translatesAutoresizingMaskIntoConstraints na NO w widoku głównym, zaczynam dostawać błędy - lub gorzej.

Więc zamiast tego używam rozwiązania hybrydowego. Ustawiam ramki i używam automatyczne pozycjonowanie i rozmiar widoku głównego w układzie skonfigurowanym inaczej przez układ automatyczny. Na przykład, oto jak Ładuję kontroler widoku strony jako kontroler widoku potomnego w viewDidLoad w aplikacji używającej układu automatycznego:
self.pageViewController = ...  
...

[self addChildViewController:self.pageViewController];
[self.view addSubview:self.pageViewController.view];

// could not get constraints to work here (using autoresizing mask)
self.pageViewController.view.frame = self.view.bounds;

[self.pageViewController didMoveToParentViewController:self];

W ten sposób Apple ładuje kontroler widoku potomnego w szablonie Xcode "aplikacja oparta na stronach" –i odbywa się to w projekcie z włączonym układem automatycznym.

Więc na Twoim miejscu spróbowałbym ustawić ramki, aby animować Przejście kontrolera widoku i zobaczymy, co się stanie. Daj znać, jak to działa.

 4
Author: bilobatum,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-01-02 23:04:18