Это вопрос и решение partial.
* Пример проекта здесь:
https://github.com/JosephLin/TransitionTest
Проблема 1:
При использовании transitionFromViewController:... макеты, сделанные toViewController viewWillAppear:, не отображаются, когда начинается анимация перехода. Другими словами, представление перед макетом отображается во время анимации, и это содержимое привязывается к позициям после макета после анимации.
Проблема 2:
Если я настраиваю фон моего навигатора UIBarButtonItem, кнопка панели отображается с неправильным размером/позицией перед анимацией и привязывается к правильному размеру/позиции, когда анимация заканчивается, аналогично задаче 1.
Чтобы продемонстрировать проблему, я создал пользовательский контейнерный контроллер, который выполняет некоторые пользовательские переходы. Это в значительной степени копия UINavigationController, которая перекрестно растворяется вместо push-анимации между представлениями.
Метод "Push" выглядит следующим образом:
- (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];
}];
}
Теперь, DetailViewController (контроллер просмотра, на который я нажимаю), нужно разместить его содержимое в viewWillAppear:. Он не может сделать это в viewDidLoad, потому что в то время у него не было бы правильного кадра.
Для демонстрационной цели DetailViewController устанавливает свою метку в разные местоположения и цвета в viewDidLoad, viewWillAppear и 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];
}
Теперь, когда вы нажимаете DetailViewController, я ожидаю увидеть метку в y =200 в начале анимации (левое изображение), а затем перейдет к y = 350 после завершения анимации (правое изображение).

Ожидаемый просмотр до и после анимации.
Однако метка находилась в y=50, как если бы макет, сделанный в viewWillAppear, не сделал его до того, как анимация состоялась (левое изображение). Но обратите внимание, что фон метки был установлен на желтый (цвет, указанный viewWillAppear)!

Неверный макет в начале анимации. Обратите внимание, что кнопки панели также начинаются с неправильного положения/размера.
Журнал консоли
TransitionTest [49795: c07] - [DetailViewController viewDidLoad]
TransitionTest [49795: c07] До переходаFromViewController:
TransitionTest [49795: c07] - [DetailViewController viewWillAppear:]
TransitionTest [49795: c07] - просмотр [DetailViewControllerWillLayoutSubviews]
TransitionTest [49795: c07] - [DetailViewController viewDidLayoutSubviews]
TransitionTest [49795: c07] - [DetailViewController viewDidAppear:]
Обратите внимание, что viewWillAppear: был вызван AFTER transitionFromViewController:
Решение проблемы 1
Хорошо, вот часть частичного решения. Явным образом вызывая beginAppearanceTransition: и endAppearanceTransition до toViewController, представление будет иметь правильную компоновку до того, как произойдет анимация перехода:
- (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];
}];
}
Обратите внимание, что viewWillAppear: теперь называется ПЕРЕД transitionFromViewController:
TransitionTest [18398: c07] - [DetailViewController viewDidLoad]
TransitionTest [18398: c07] - [DetailViewController viewWillAppear:]
TransitionTest [18398: c07] До переходаFromViewController:
TransitionTest [18398: c07] - просмотр [DetailViewControllerWillLayoutSubviews]
TransitionTest [18398: c07] - [DetailViewController viewDidLayoutSubviews]
TransitionTest [18398: c07] - [DetailViewController viewDidAppear:]
Но это не устраняет проблему 2!
По какой-то причине кнопки навигации по-прежнему начинаются с неправильного положения/размера в начале анимации перехода. Я потратил так много времени, пытаясь найти правильное решение, но без везения. Я начинаю чувствовать, что это ошибка в transitionFromViewController: или UIAppearance или что-то еще. Пожалуйста, любое понимание, которое вы можете предложить по этому вопросу, очень ценится. Спасибо!
Другие решения, которые я пробовал
- Вызов
[self.view addSubview:toViewController.view];доtransitionFromViewController:
Фактически он дает точно правильный результат пользователю, исправляет обе проблемы 1 и 2. Проблема в том, что viewWillAppear и viewDidAppear будут вызываться дважды! Это проблематично, если я хочу сделать некоторую экспансивную анимацию или вычисление в viewDidAppear.
- Вызов
[toViewController viewWillAppear:YES];доtransitionFromViewController:
Я думаю, что это почти так же, как вызов beginAppearanceTransition:. Он исправляет проблему 1, но не проблема 2. Кроме того, в документе говорится, что он не вызывает viewWillAppear напрямую!
- Используйте
[UIView animateWithDuration:]вместоtransitionFromViewController:
Вот так: [self addChildViewController: toViewController]; [self.view addSubview: toViewController.view]; toViewController.view.alpha = 0.0;
[UIView animateWithDuration:0.5 animations:^{
toViewController.view.alpha = 1.0;
} completion:^(BOOL finished) {
[toViewController didMoveToParentViewController:self];
}];
Он исправляет проблему 2, но представление начиналось с макета в viewDidAppear (метка зеленая, y = 350). Кроме того, кросс-растворение не так хорошо, как использование UIViewAnimationOptionTransitionCrossDissolve