Niestandardowy Kontroler kontenera: transitionFromViewController: Widok nieprawidłowo ustawiony przed animacją

Jest to zarówno pytanie, jak i częściowe rozwiązanie.


*przykładowy projekt tutaj:

Https://github.com/JosephLin/TransitionTest


Problem 1:

Podczas korzystania z transitionFromViewController:..., układy wykonane przez toViewController's viewWillAppear: nie pojawiają się, gdy rozpoczyna się animacja przejścia. Innymi słowy, widok pre-layout jest wyświetlany podczas animacji, a jego zawartość przyciąga się do pozycji po Układzie po animacja.

Problem 2:

Jeśli dostosuję tło mojego paska nawigacyjnego UIBarButtonItem, przycisk paska pojawi się z niewłaściwym rozmiarem/pozycją przed animacją i przyciągnie się do właściwego rozmiaru/pozycji po zakończeniu animacji, podobnie jak Problem 1.


Aby zademonstrować problem, stworzyłem Niestandardowy kontroler kontenerów, który wykonuje niestandardowe przejścia widoku. Jest to prawie Kopia kontrolera UINavigationController, która zamiast push-dissolve krzyżuje się animacja między widokami.

Metoda 'Push' wygląda tak:

- (void)pushController:(UIViewController *)toViewController
{
    UIViewController *fromViewController = [self.childViewControllers lastObject];

    [self addChildViewController:toViewController];
    toViewController.view.frame = self.view.bounds;

    NSLog(@"Before transitionFromViewController:");
    [self transitionFromViewController:fromViewController
                      toViewController:toViewController
                              duration:0.5
                               options:UIViewAnimationOptionTransitionCrossDissolve
                            animations:^{}
                            completion:^(BOOL finished) {
                                [toViewController didMoveToParentViewController:self];
                            }];
}

Teraz DetailViewController (kontroler widoku, do którego naciskam) musi układać zawartość w viewWillAppear:. Nie może tego zrobić w viewDidLoad, ponieważ nie miałby wtedy poprawnej klatki.

Do celów demonstracyjnych, DetailViewController ustawia swoją etykietę na różne miejsca i kolory w viewDidLoad, viewWillAppear, i viewDidAppear:

- (void)viewDidLoad
{
    [super viewDidLoad];
    NSLog(@"%s", __PRETTY_FUNCTION__);
    CGRect rect = self.descriptionLabel.frame;
    rect.origin.y = 50;
    self.descriptionLabel.frame = rect;
    self.descriptionLabel.text = @"viewDidLoad";
    self.descriptionLabel.backgroundColor = [UIColor redColor];
}

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    NSLog(@"%s", __PRETTY_FUNCTION__);
    CGRect rect = self.descriptionLabel.frame;
    rect.origin.y = 200;
    self.descriptionLabel.frame = rect;
    self.descriptionLabel.text = @"viewWillAppear";
    self.descriptionLabel.backgroundColor = [UIColor yellowColor];
}

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
    NSLog(@"%s", __PRETTY_FUNCTION__);
    CGRect rect = self.descriptionLabel.frame;
    rect.origin.y = 350;
    self.descriptionLabel.frame = rect;
    self.descriptionLabel.text = @"viewDidAppear";
    self.descriptionLabel.backgroundColor = [UIColor greenColor];
}

Teraz, gdy naciskam DetailViewController, spodziewam się zobaczyć etykietę na {[16] } w początek animacji (lewy obrazek), a następnie przeskakuje do y = 350 po zakończeniu animacji (prawy obrazek).

Oczekiwany Widok przed i po animacji.


Jednak etykieta była na y=50, tak jakby layout wykonany w viewWillAppear nie powstał przed animacją (lewy obrazek). Zwróć jednak uwagę, że tło etykiety zostało ustawione na żółty (kolor określony przez viewWillAppear)!

Nieprawidłowy układ na początku animacji. Zauważ, że przyciski paska również zaczynają się od niewłaściwej pozycji / rozmiaru.


Log Konsoli
TransitionTest[49795: c07]- [DetailViewController viewDidLoad]
TransitionTest [49795: c07] przed transitionFromViewController:
TransitionTest[49795: c07]- [DetailViewController viewWillAppear:]
TransitionTest[49795: c07]- [DetailViewController viewWillLayoutSubviews]
TransitionTest[49795: c07]- [DetailViewController viewDidLayoutSubviews]
TransitionTest[49795: c07]- [DetailViewController viewDidAppear:]
zauważ, że viewWillAppear: został nazwany po transitionFromViewController:


Rozwiązanie problemu 1

W porządku, nadchodzi częściowy część rozwiązania. Poprzez jawne wywołanie beginAppearanceTransition: i endAppearanceTransition na toViewController, widok będzie miał prawidłowy układ przed animacją przejścia:

- (void)pushController:(UIViewController *)toViewController
{
    UIViewController *fromViewController = [self.childViewControllers lastObject];

    [self addChildViewController:toViewController];
    toViewController.view.frame = self.view.bounds;

    [toViewController beginAppearanceTransition:YES animated:NO];

    NSLog(@"Before transitionFromViewController:");
    [self transitionFromViewController:fromViewController
                      toViewController:toViewController
                              duration:0.5
                               options:UIViewAnimationOptionTransitionCrossDissolve
                            animations:^{}
                            completion:^(BOOL finished) {
                                [toViewController didMoveToParentViewController:self];
                                [toViewController endAppearanceTransition];
                            }];
}

Zauważ, że {[6] } jest teraz wywoływana przed transitionFromViewController: TransitionTest[18398: c07]- [DetailViewController viewDidLoad]
TransitionTest[18398: c07]- [DetailViewController viewWillAppear:]
TransitionTest[18398: c07] przed transitionFromViewController:
TransitionTest[18398: c07]- [DetailViewController viewWillLayoutSubviews]
TransitionTest[18398: c07]- [DetailViewController viewDidLayoutSubviews]
TransitionTest[18398: c07]- [DetailViewController viewDidAppear:]


Ale to nie naprawia Problem 2!

Z jakiegokolwiek powodu, przyciski paska nawigacyjnego nadal zaczynają się od niewłaściwej pozycji/rozmiaru na początku animacji przejścia. Spędziłem tyle czasu próbując znaleźć właściwe rozwiązanie, ale bez powodzenia. Zaczynam czuć, że to błąd w transitionFromViewController: lub UIAppearance czy cokolwiek. Proszę, każdy wgląd można zaoferować na to pytanie jest bardzo mile widziane. Dzięki!


Inne rozwiązania, które wypróbowałem

  • zadzwoń [self.view addSubview:toViewController.view]; przed transitionFromViewController:

To faktycznie daje dokładnie odpowiedni wynik dla użytkownika, rozwiązuje zarówno Problem 1 i 2. Problem w tym, że viewWillAppear i viewDidAppear będą wywoływane dwa razy! Jest to problematyczne, jeśli chcę zrobić jakąś ekspansywną animację lub obliczenia w viewDidAppear.

  • zadzwoń [toViewController viewWillAppear:YES]; przed transitionFromViewController:

Myślę, że to prawie to samo, co dzwonienie beginAppearanceTransition:. Rozwiązuje Problem 1, ale nie Problem 2. Do tego doktor mówi, żeby nie dzwonić bezpośrednio!

  • użyj [UIView animateWithDuration:] zamiast z transitionFromViewController:

Like this: [self addChildViewController:toViewController]; [self.widok addSubview: toViewController.widok]; toViewController.widok.Alfa = 0.0;

[UIView animateWithDuration:0.5 animations:^{
    toViewController.view.alpha = 1.0;
} completion:^(BOOL finished) {
    [toViewController didMoveToParentViewController:self];
}];

Rozwiązuje Problem 2, ale widok zaczynał się od układu w viewDidAppear (etykieta jest zielona, y=350). Ponadto krzyż-rozpuszczanie nie jest tak dobre, jak przy użyciu UIViewAnimationOptionTransitionCrossDissolve

Author: Joseph Lin, 2013-01-31

2 answers

Ok, dodanie layoutIfNeeded do kontrolera toViewController.widok wydaje się robić sztuczkę-to dostaje widok ułożone prawidłowo, zanim pojawi się na ekranie (bez Dodaj/usuń), i nie więcej dziwne podwójne viewdidappear: call.

- (void)pushController:(UIViewController *)toViewController
{
    UIViewController *fromViewController = [self.childViewControllers lastObject];

    [self addChildViewController:toViewController];

    toViewController.view.frame = self.view.bounds;
    [toViewController.view layoutIfNeeded];

    NSLog(@"Before transitionFromViewController:");
    [self transitionFromViewController:fromViewController
                      toViewController:toViewController
                              duration:0.5
                               options:UIViewAnimationOptionTransitionCrossDissolve
                            animations:^{}
                            completion:^(BOOL finished) {
                            }];

}
 7
Author: escrafford,
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
2013-02-06 17:20:29

Miał ten sam problem, wszystko czego potrzebujesz to przekierowanie transakcji wygląd i UIView.Animować. Takie podejście rozwiązuje wszystkie problemy, nie tworzy nowych. Oto trochę kodu C# (xamarin):

var fromView = fromViewController.View;
var toView = toViewController.View;

fromViewController.WillMoveToParentViewController(null);
AddChildViewController(toViewController);

fromViewController.BeginAppearanceTransition(false, true);
toViewController.BeginAppearanceTransition(true, true);

var frame = fromView.Frame;
frame.X = -viewWidth * direction;
toView.Frame = frame;
View.Add(toView);

UIView.Animate(0.3f,
    animation: () =>
    { 
        toView.Frame = fromView.Frame; 
        fromView.MoveTo(x: viewWidth * direction);
    },
    completion: () =>
    {
        fromView.RemoveFromSuperview();

        fromViewController.EndAppearanceTransition();
        toViewController.EndAppearanceTransition();

        fromViewController.RemoveFromParentViewController();
        toViewController.DidMoveToParentViewController(this);
    }
);

I oczywiście należy wyłączyć automatyczne przekazywanie metod wyglądu i zrobić to ręcznie:

public override bool ShouldAutomaticallyForwardAppearanceMethods
{
    get { return false; }
}

public override void ViewWillAppear(bool animated)
{
    base.ViewWillAppear(animated);
    CurrentViewController.BeginAppearanceTransition(true, animated);
}

public override void ViewDidAppear(bool animated)
{
    base.ViewDidAppear(animated);
    CurrentViewController.EndAppearanceTransition();
}

public override void ViewWillDisappear(bool animated)
{
    base.ViewWillDisappear(animated);
    CurrentViewController.BeginAppearanceTransition(false, animated);
}

public override void ViewDidDisappear(bool animated)
{
    base.ViewDidDisappear(animated);
    CurrentViewController.EndAppearanceTransition();
}
 2
Author: Alexander Danilov,
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
2016-01-09 11:39:31