ios - Controlador de contenedor personalizado: transitionFromViewController: la vista no se distribuye correctamente antes de la animaciĆ³n
xcode uiviewcontroller (2)
Bien, agregar layoutIfNeeded al toViewController.view parece hacer el truco: esto hace que la vista se muestre correctamente antes de que aparezca en la pantalla (sin agregar / quitar), y no hay más doble doble 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) {
}];
}
Esto es tanto una pregunta como una solución parcial .
* Proyecto de muestra aquí:
https://github.com/JosephLin/TransitionTest
Problema 1:
Cuando se utiliza la organización toViewController
transitionFromViewController:...
, los diseños realizados por toViewController
''s viewWillAppear:
no se muestran cuando comienza la animación de transición. En otras palabras, la vista previa al diseño se muestra durante la animación, y su contenido se ajusta a las posiciones posteriores al diseño después de la animación.
Problema 2:
Si personalizo el fondo del UIBarButtonItem de mi barra de UIBarButtonItem
, el botón de la barra aparece con el tamaño / posición incorrecto antes de la animación, y se ajusta al tamaño / posición correcta cuando finaliza la animación, similar al Problema 1.
Para demostrar el problema, hice un controlador de contenedor personalizado básico que realiza algunas transiciones de vista personalizadas. Es prácticamente una copia de UINavigationController que se disuelve de forma cruzada en lugar de empujar la animación entre vistas.
El método ''Push'' se ve así:
- (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];
}];
}
Ahora, DetailViewController
(el controlador de vista al que estoy presionando) necesita diseñar su contenido en viewWillAppear:
No puede hacerlo en viewDidLoad
porque no tendría el marco correcto en ese momento.
Para fines de demostración, DetailViewController
establece su etiqueta en diferentes ubicaciones y colores en viewDidLoad
, viewWillAppear
y 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];
}
Ahora, al presionar DetailViewController
, espero ver la etiqueta en y =200
al comienzo de la animación (imagen de la izquierda), y luego salta a y = 350
después de que finaliza la animación (imagen de la derecha).
Vista esperada antes y después de la animación.
Sin embargo, la etiqueta estaba en y=50
, como si el diseño hecho en viewWillAppear
no lo hiciera antes de que se llevara a cabo la animación (imagen de la izquierda). ¡Pero observe que el fondo de la etiqueta se estableció en amarillo (el color especificado por viewWillAppear
)!
Diseño incorrecto al inicio de la animación. Observe que los botones de la barra también comienzan con la posición / tamaño incorrecto.
Registro de la consola
TransitionTest [49795: c07] - [DetailViewController viewDidLoad]
TransitionTest [49795: c07] Antes de transitionFromViewController:
TransitionTest [49795: c07] - [DetailViewController viewWillAppear:]
TransitionTest [49795: c07] - [DetailViewController viewWillLayoutSubviews]
TransitionTest [49795: c07] - [DetailViewController viewDidLayoutSubviews]
TransitionTest [49795: c07] - [DetailViewController viewDidAppear:]
Observe que viewWillAppear:
se llamó AFTER transitionFromViewController:
Solución para el problema 1
Muy bien, aquí viene la parte de la solución parcial. Al llamar explícitamente beginAppearanceTransition:
y endAppearanceTransition
a toViewController
, la vista tendrá el diseño correcto antes de que tenga lugar la animación de transición:
- (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];
}];
}
Observe que viewWillAppear:
ahora se llama BEFORE transitionFromViewController:
TransitionTest [18398: c07] - [DetailViewController viewDidLoad]
TransitionTest [18398: c07] - [DetailViewController viewWillAppear:]
TransitionTest [18398: c07] Before TransitionFromViewController:
TransitionTest [18398: c07] - [DetailViewController viewWillLayoutSubviews]
TransitionTest [18398: c07] - [DetailViewController viewDidLayoutSubviews]
TransitionTest [18398: c07] - [DetailViewController viewDidAppear:]
¡Pero eso no soluciona el problema 2!
Por cualquier motivo, los botones de la barra de navegación aún comienzan con la posición / tamaño incorrecto al comienzo de la animación de transición. Pasé mucho tiempo tratando de encontrar LA solución correcta pero sin suerte. Estoy empezando a sentir que es un error en la transitionFromViewController:
UIAppearance
transitionFromViewController:
UIAppearance
o lo que sea. Por favor, cualquier información que pueda ofrecer a esta pregunta es muy apreciada. ¡Gracias!
Otras soluciones que he probado
- Llame a
[self.view addSubview:toViewController.view];
antes de latransitionFromViewController:
controlador detransitionFromViewController:
En realidad, da exactamente el resultado correcto al usuario, soluciona tanto el problema 1 como el 2. El problema es que viewWillAppear
y viewDidAppear
se viewDidAppear
dos veces. Es problemático si quiero hacer alguna animación o cálculo expansivo en viewDidAppear
.
- Llame a
[toViewController viewWillAppear:YES];
antes de latransitionFromViewController:
controlador detransitionFromViewController:
Creo que es casi lo mismo que llamar a beginAppearanceTransition:
Corrige el problema 1, pero no el problema 2. Además, el documento dice que no debe llamar a viewWillAppear
directamente!
- Use
[UIView animateWithDuration:]
lugar detransitionFromViewController:
Así: [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];
}];
Corrige el problema 2, pero la vista comenzó con el diseño en viewDidAppear
(la etiqueta es verde, y = 350). Además, la disolución cruzada no es tan buena como utilizar UIViewAnimationOptionTransitionCrossDissolve
Tuvo el mismo problema, todo lo que necesita es reenviar las transacciones de apariencia y UIView.Animate. Este enfoque soluciona todos los problemas, no crea otros nuevos. Aquí hay un código 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);
}
);
y, por supuesto, debe deshabilitar el reenvío automático de métodos de apariencia y hacerlo manualmente:
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();
}