“From View Controller” desaparece con UIViewControllerContextTransitioning

Tengo un problema y lo he descrito a continuación.

Estoy usando UIViewControllerContextTransitioning para transiciones personalizadas.

Tengo 2 controladores de visualización, primer controlador de vista y segundo controlador de vista.

Ahora quiero agregar controlador secondview en el controlador de primera vista con animación. Lo he logrado, ahora tengo el controlador de segunda vista transparente, por lo que podemos ver el controlador de vista primero debajo del controlador de segunda vista.

Pero no puedo ver el controlador de vista primero, y puedo ver solo la pantalla en negro debajo del controlador de segunda vista.

Aquí está el código.

-(void)animateTransition:(id)transitionContext{ self.transitionContext = transitionContext; if(self.isPresenting){ [self executePresentationAnimation:transitionContext]; } else{ [self executeDismissalAnimation:transitionContext]; } } -(void)executePresentationAnimation:(id)transitionContext{ UIView* inView = [transitionContext containerView]; UIViewController* toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey]; UIViewController* fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey]; CGRect offScreenFrame = inView.frame; offScreenFrame.origin.y = inView.frame.size.height; toViewController.view.frame = offScreenFrame; toViewController.view.backgroundColor = [UIColor clearColor]; fromViewController.view.backgroundColor = [UIColor clearColor]; inView.backgroundColor = [UIColor clearColor]; [inView insertSubview:toViewController.view aboveSubview:fromViewController.view]; // [inView addSubview:toViewController.view]; CFTimeInterval duration = self.presentationDuration; CFTimeInterval halfDuration = duration/2; CATransform3D t1 = [self firstTransform]; CATransform3D t2 = [self secondTransformWithView:fromViewController.view]; [UIView animateKeyframesWithDuration:halfDuration delay:0.0 options:UIViewKeyframeAnimationOptionCalculationModeLinear animations:^{ [UIView addKeyframeWithRelativeStartTime:0.0f relativeDuration:0.5f animations:^{ fromViewController.view.layer.transform = t1; }]; [UIView addKeyframeWithRelativeStartTime:0.5f relativeDuration:0.5f animations:^{ fromViewController.view.layer.transform = t2; }]; } completion:^(BOOL finished) { }]; [UIView animateWithDuration:duration delay:(halfDuration - (0.3*halfDuration)) usingSpringWithDamping:0.7f initialSpringVelocity:6.0f options:UIViewAnimationOptionCurveEaseIn animations:^{ toViewController.view.frame = inView.frame; } completion:^(BOOL finished) { [self.transitionContext completeTransition:YES]; }]; } 

Cuando [self.transitionContext completeTransition:YES]; llamado, de repente desaparece el primer controlador de vista y aparece una pantalla negra debajo del segundo controlador de vista.

¿Alguien tiene idea? Gracias.

Estaba teniendo el mismo problema aquí: parece un error en iOS 8. He archivado un radar .

Utilicé Reveal para inspeccionar la jerarquía de vista después de que la pantalla se vuelve negra. La UIWindow clave está completamente vacía, ¡no hay jerarquía de vistas en absoluto!

Revelado

Jugué un poco y parece que hay una solución fácil, para casos simples. Puede volver a agregar la vista de toViewController como una subvista de la ventana de la clave:

 transitionContext.completeTransition(true) UIApplication.sharedApplication().keyWindow!.addSubview(toViewController.view) 

Lo he verificado y rootViewController la ventana clave todavía está configurado correctamente, así que está bien. No estoy seguro de qué pasaría si presentaras tu controlador desde un controlador modal ya presentado, por lo que para casos más complejos, tendrás que experimentar.

Siento que el razonamiento detrás de esto debería explicarse mejor.

La vista desaparece porque quita la vista del controlador de visualización de presentación de su ubicación original (jerarquía de vista), la coloca dentro de la vista de contenedor que proporciona su animador pero nunca la devuelve después de que la animación ha finalizado. Para que la vista del controlador de vista se elimine por completo con su supervista (contenedorView) desde la ventana.

En iOS 7, el sistema siempre devuelve las vistas de los controladores de vista que están involucradas en la presentación (presentación y presentación) a sus lugares originales una vez que la transición ha terminado de animar automáticamente. Eso ya no sucede con algunos estilos de presentación en iOS 8.

La regla es muy simple: el animador solo debe manipular la vista del controlador de la vista presentadora si la vista de ese controlador de vista va a estar oculta (eliminada de la jerarquía de vista) completamente al final de la transición . En otras palabras, significa que después de que finalice la animación de la presentación inicial, solo se verá la vista del controlador de vista presentado y no la vista del controlador de vista que se está presentando. Por ejemplo, si configura la opacidad de vista del controlador de vista presentada al 50% y utiliza UIModalPresentationFullScreen, no podrá ver la vista del controlador de vista debajo del presentado, pero si usa UIModalPresentationOverFullscreen, (el método shouldRemovePresentersView es responsable de especificar eso).

¿Por qué no permitir que el animador manipule la vista del controlador de la vista de presentación en todo momento? En primer lugar, si la vista del controlador de la vista de presentación va a permanecer visible después de que finalice la animación durante todo el ciclo de vida de la presentación, no hay necesidad de animarla en absoluto; simplemente permanece donde está. Segundo, si la propiedad de ese controlador de vista se transfiere al controlador de presentación, lo más probable es que el controlador de presentación no sepa cómo distribuir la vista del controlador de vista cuando sea necesario, por ejemplo, cuando cambie la orientación, pero el propietario original del controlador de vista presentador .

En iOS 8 se introdujo el método viewForKey: para obtener vistas que el animador manipula. En primer lugar, ayuda seguir la regla descrita anteriormente devolviendo nil cada vez que el animador no debe tocar la vista. En segundo lugar, puede devolver una vista diferente para animar al animador. Imagine que está implementando una presentación similar a la hoja de formulario. En este caso, querrá agregar alguna sombra o decoración alrededor de la vista del controlador de visualización presentado. El animador animará esa decoración en su lugar y la vista del controlador de vista presentada será un elemento secundario de la decoración.

viewControllerForKey: no desaparece, aún se puede usar si se necesita un acceso directo para ver los controladores, pero el animador no debe hacer suposiciones sobre las vistas que necesita para animar.

Hay varias cosas que puede hacer para corregir correctamente un problema con la desaparición de la vista del controlador de vista que se presenta cuando la coloca explícitamente dentro de la vista de contenedor del animador:

  1. Si no necesita animar la vista del controlador de la vista que se presenta, use viewForKey: para obtener vistas para animar en lugar de extender la mano para ver las vistas del controlador directamente. viewForKey: puede devolver vistas nulas o incluso completamente diferentes.

  2. Si desea animar la vista de los controladores de vista de presentación, debería considerar usar el estilo UIModalPresentationFullScreen o continuar usando UIModalPresentationCustom e implementar su propia subclase de UIPresentationController con shouldRemovePresentersView devolviendo YES . De hecho, la implementación de este método es la diferencia principal entre los controladores de presentación internos definidos por UIModalPresentationFullScreen y UIModalPresentationCustom styles, aparte del hecho de que este último le permite usar controladores de presentación personalizados.

  3. En todos los demás casos excepcionales, tendrá que devolver la vista del controlador de la vista de presentación a su ubicación original, como sugieren otras respuestas.

En iOS 8, debe manipular las vistas devueltas por viewForKey: lugar de la propiedad .view de los controladores de vista devueltos por viewControllerForKey: Esto no está particularmente claro en la documentación de la versión beta, pero si nos fijamos en el origen de UIViewControllerTransitioning.h, verá este comentario arriba viewControllerForKey: :

 // Currently only two keys are defined by the // system - UITransitionContextToViewControllerKey, and // UITransitionContextFromViewControllerKey. // Animators should not directly manipulate a view controller's views and should // use viewForKey: to get views instead. - (UIViewController *)viewControllerForKey:(NSString *)key; 

Entonces, en lugar de ajustar marcos, etc. de toViewController.view , use el valor de retorno de [transitionContext viewForKey:UITransitionContextToViewKey] .

Si su aplicación necesita ser compatible con iOS7 y / o Xcode 5, entonces puede usar un método de categoría simple en UIViewController como el siguiente:

 - (UIView *)viewForTransitionContext:(id)transitionContext { #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 if ([transitionContext respondsToSelector:@selector(viewForKey:)]) { NSString *key = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey] == self ? UITransitionContextFromViewKey : UITransitionContextToViewKey; return [transitionContext viewForKey:key]; } else { return self.view; } #else return self.view; #endif } 

Luego, lleve su toViewController y fromViewController como de costumbre, pero obtenga las vistas usando [toViewController viewForTransitionContext:transitionContext] .

Editar: Parece que hay un error, donde la vista del controlador de la vista de presentación es nula cuando se devuelve desde viewForKey , lo que evita que realice transiciones modales que animan la vista de presentación (como deslizamiento o flip-horizontal). Archivé un error para iOS8 en rdar: // 17961976 ( http://openradar.appspot.com/radar?id=5210815787433984 ). También vea el proyecto de muestra en http://github.com/bcherry/TransitionBug

Editar 2: Gracias a graveley por la sugerencia, el uso de UIModalPresentationFullScreen corrige el problema. Quizás esto no es un error. Apple puede pretender que UIModalPresentationCustom solo modifique la vista del modal entrante. Si desea modificar la vista saliente, debe garantizar la presentación en pantalla completa de la nueva vista. En cualquier caso, debe usar viewForKey y UIModalPresentationFullScreen.

No establezco modalPresentationStyle en UIModalPresentationCustom solucionado el problema por mí.

En otras palabras, si se deja el valor predeterminado de UIModalPresentationFullScreen en lugar de especificar UIModalPresentationCustom, se solucionó el problema de desaparición de la vista. Tenga en cuenta que el protocolo UIViewControllerTransitioningDelegate parece seguir incluso cuando se deja esto en el valor predeterminado. Si recuerdo correctamente, alguna vez UIModalPresentationCustom era un requisito.

Funciona hasta ahora, solo he probado esto para animaciones no interactivas.

He encontrado esta respuesta extremadamente útil en un hilo relacionado por Lefteris: https://stackoverflow.com/a/27165723/3709173

En resumen:

  1. establecer modalPresentationStyle en .Custom
  2. subclase UIPresentationController, anulación shouldRemovePresentersView (con NO)
  3. anula presentationControllerForPresentedViewController en tu clase TransitionDelegate y devuelve tu UIPresentationController personalizado

+1 en su transición personalizada, no agregue a Vista cuando se produzca la animación de descarte.

Demostrado aquí:

https://www.dropbox.com/s/7rpkyamv9k9j18v/CustomModalTransition.zip?dl=0 sin ningún truco. es como magia! 🙂

En iOS 8, necesita crear un UIPresentationController e implementar el método a continuación, en UIViewControllerTransitioningDelegate.

 - (UIPresentationController *)presentationControllerForPresentedViewController:(UIViewController *)presented presentingViewController:(UIViewController *)presenting sourceViewController:(UIViewController *)source; 

Pide a su delegado que use el controlador de presentación personalizado para administrar la jerarquía de vista al presentar un controlador de vista.

Valor de retorno:

El controlador de presentación personalizado para gestionar la presentación modal.

Discusión:

Cuando presenta un controlador de vista utilizando el estilo de presentación UIModalPresentationCustom, el sistema llama a este método y le pide el controlador de presentación que administra su estilo personalizado. Si implementa este método, utilícelo para crear y devolver el objeto de controlador de presentación personalizado que desea usar para administrar el proceso de presentación.

Si no implementa este método, o si su implementación de este método devuelve cero, el sistema usa un objeto de controlador de presentación predeterminado. El controlador de presentación predeterminado no agrega vistas o contenido a la jerarquía de vistas.

Disponibilidad Disponible en iOS 8.0 y posterior.

Para más información, vea el video WWDC 2014:

https://developer.apple.com/videos/wwdc/2014/?include=228

También hay un código de muestra de la WWDC llamado “LookInside: adaptación de los controladores de presentación y objetos personalizados del animador”, que puede descargar de la página de códigos de muestra de la WWDC 2014.

Es posible que necesite cambiar el código de muestra un poco. El método de inicio de UIPresentationController cambió a:

 initWithPresentedViewController:presented presentingViewController:presenting 

Antes de presentar y luego presentar. Simplemente cambiarlos y debería funcionar.

en lugar de [inView insertSubview: toViewController.view aboveSubview: fromViewController.view]; solo agregue: [inView addSubview: toViewController.view];

 if (self.presenting) { [transitionContext.containerView addSubview:toViewController.view]; // your code } else { // your code } 

Puede ver un ejemplo aquí: enlace y funciona en iOS 7 e iOS 8

Aquí hay una versión del Objetivo C de la solución de Ash.

 // my attempt at obj-c version of Ash's fix UIView *theToView = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey].view; [[[UIApplication sharedApplication] keyWindow] addSubview:theToView]; [transitionContext completeTransition:YES] 

Tuve que cambiar el orden y llamar al método [transitionContext completeTransition:] después de volver a agregar la vista para presentar un nuevo controlador de vista desde el bloque de finalización de cierre de otro controlador de vista para que funcione correctamente.

No sé si esto lo solucionará todo el mundo, pero funciona en mi aplicación. ¡Aclamaciones!

Encontré que esto funcionó bien para Obj-C:

  [transitionContext completeTransition:YES]; if(![[UIApplication sharedApplication].keyWindow.subviews containsObject:toViewController.view]) { [[UIApplication sharedApplication].keyWindow addSubview:toViewController.view]; } 

Parece funcionar bien tanto en iOS7 como en iOS8.

Encontré que viewForKey:UITransitionContextToViewKey devuelve nil en ios8. Entonces, si es nula, tomo la vista desde el controlador ‘to’ view.

Sin embargo, esto parece dar como resultado que la vista ‘a’ no se mueva del contenedor a la ventana cuando se realiza la Transición completeTransition:YES . Entonces, si viewForKey:UITransitionContextToViewKey devuelve nil, yo me toVC.view a toVC.view , y toVC.view un seguimiento del hecho de que devolvió nulo, y después de la finalización lo muevo a la supervista inicial del contenedor (que pasa a ser la ventana).

Por lo tanto, este código funciona tanto en iOS7 como en iOS8, y también debería funcionar en iOS9 aunque lo arreglen o no.

 - (void)animateTransition:(id )transitionContext { // Get the 'from' and 'to' views/controllers. UIViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey]; UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey]; BOOL hasViewForKey = [transitionContext respondsToSelector:@selector(viewForKey:)]; // viewForKey is iOS8+. UIView *fromView = hasViewForKey ? [transitionContext viewForKey:UITransitionContextFromViewKey] : fromVC.view; UIView *toView = hasViewForKey ? [transitionContext viewForKey:UITransitionContextToViewKey] : toVC.view; // iOS8 has a bug where viewForKey:to is nil: http://stackoverflow.com/a/24589312/59198 // The workaround is: A) get the 'toView' from 'toVC'; B) manually add the 'toView' to the container's // superview (eg the root window) after the completeTransition call. BOOL toViewNilBug = !toView; if (!toView) { // Workaround by getting it from the view. toView = toVC.view; } UIView *container = [transitionContext containerView]; UIView *containerSuper = container.superview; // Used for the iOS8 bug workaround. // Perform the transition. toView.frame = container.bounds; [container insertSubview:toView belowSubview:fromView]; [UIView animateWithDuration:kDuration delay:0 options:UIViewAnimationOptionCurveEaseIn animations:^{ fromView.frame = CGRectOffset(container.bounds, 0, CGRectGetHeight(container.bounds)); } completion:^(BOOL finished) { [transitionContext completeTransition:YES]; if (toViewNilBug) { [containerSuper addSubview:toView]; } }]; } 

Descubrí que este error (y muchos más) desaparece si configura modalPresentationStyle = UIModalPresentationFullScreen . Por supuesto, todavía obtienes tu animación de transición personalizada.

Después de encontrarme con este problema, estaba muy confundido, porque había escrito algo casi idéntico no hace mucho tiempo que funcionaba bien. Vine aquí en busca de respuestas para encontrar soluciones que parecen bastante hacky, y no parecen entender la causa raíz … en realidad es muy fácil de solucionar.

Algunas respuestas mencionan el cambio modalPresentationStyle a .overFullScreen . Esto es correcto, .overCurrentContext también funcionaría. Esto es esperado, y el comportamiento de los documentos de Apple. Pero, ¿por qué no funciona para todos? ¿Por qué todo el código hacky, y combinaciones de esto con algo más, y cosas locas que no deberías estar haciendo?

Resulta que debe establecer el estilo de presentación ANTES DE VER CARGAS . No después de. Hazlo en init, o hazlo desde el controlador anterior, o como quieras, siempre que sea antes de que se cargue la vista.

El uso del nuevo UIModalPresentationOverCurrentContext me lo arregló. Mi transición original en iOS 7 fue solo para tener un fondo borroso de la vista debajo del modal.

Me quedé atrapado en este tema también. Estaba buscando crear una transición personalizada con un fondo semitransparente donde todavía pudiera ver el controlador de vista del que venía pero solo obtuve un fondo negro. Encontré que la respuesta de Mark Aron en este hilo me ayudó, pero está escrita en Objective C, así que aquí hay una versión Swift 3 de esa respuesta que he probado para iOS 9 y iOS 10:

  1. Crea una subclase de UIPresentationController. Reemplace el shouldRemovePresentersView por falso de la siguiente manera:

     class ModalPresentationController: UIPresentationController { override var shouldRemovePresentersView: Bool { return false } override func containerViewWillLayoutSubviews() { presentedView?.frame = frameOfPresentedViewInContainerView } } 
  2. En el lugar en el que está instanciando el nuevo controlador de vista y configurando su delegado de transición, indique que desea que muestre un estilo de presentación modal personalizado de la siguiente manera:

     let newVC = mainStoryboard.instantiateViewController(withIdentifier: "newVC") as! NewViewController newVC.transitioningDelegate = self newVC.modalPresentationStyle = UIModalPresentationStyle.custom newVC.modalPresentationCapturesStatusBarAppearance = true //optional present(newVC, animated: true, completion: nil) 
  3. Ahora anule el método presentationController de su UIViewControllerTransitioningDelegate y devuelva su UIPresentationController personalizado. Yo tenía el mío como una extensión de mi clase actual:

     extension CurrentViewController: UIViewControllerTransitioningDelegate { //this is where you implement animationController(forPresented) and animationController(forDismissed) methods func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? { return ModalPresentationController(presentedViewController: presented, presenting: source) } } 

Otra cosa que debe tener en cuenta es que no debe intentar y hacer referencia a su fromView en su presente clase de Animador. Esto será nulo y obtendrá un error en el tiempo de ejecución. Aparte de eso, si implementa cosas como las cosas, obtendrá su transición personalizada con su animación y un fondo semitransparente si realiza una.

agrega un controlador de vista como hijo de otro controlador de vista.

 [self addChildViewController:childViewController]; 

revisa y avísame.