Это вопрос и решение 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