Пользовательский Контейнерный Контроллер: transitionFromViewController: Представление не правильно расположено перед анимацией
Это и вопрос, и частичное решение.
* Пример проекта здесь:
https://github.com/JosephLin/TransitionTest
Проблема 1:
Когда используешь transitionFromViewController:...
макеты сделаны toViewController
"s viewWillAppear:
не отображается, когда начинается анимация перехода. Другими словами, представление перед макетом отображается во время анимации, и его содержимое привязывается к позициям после макета после анимации.
Проблема 2:
Если я настрою фон моего navbar's 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] Перед transitionFromViewController:
TransitionTest [49795: c07] - [Представление DetailViewControllerWillAppear:]
TransitionTest [49795: c07] - [DetailViewController viewWillLayoutSubviews]
TransitionTest [49795: c07] - [DetailViewController viewDidLayoutSubviews]
TransitionTest [49795: c07] - [DetailViewController viewDidAppear:]
Заметить, что viewWillAppear:
был назван ПОСЛЕ 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] - [Представление DetailViewControllerWillAppear:]
TransitionTest [18398: c07] Перед transitionFromViewController:
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
(метка зеленая, у =350). Кроме того, перекрестное растворение не так хорошо, как при использовании UIViewAnimationOptionTransitionCrossDissolve
2 ответа
Хорошо, добавление layoutIfNeeded в toViewController.view, кажется, делает свое дело - это правильно отображает представление, прежде чем оно появится на экране (без добавления / удаления), и больше не будет странного двойного 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) {
}];
}
Возникла та же проблема, все, что вам нужно, это пересылка транзакций внешнего вида и UIView.Animate. Такой подход решает все проблемы, не создает новых. Вот код 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);
}
);
и, конечно, вы должны отключить автоматическую пересылку методов внешнего вида и сделать это вручную:
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();
}