"From View Controller" desaparece usando UIViewControllerContextTransitioning


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

Estoy usando UIViewControllerContextTransitioning para transiciones personalizadas.

Tengo 2 controladores de vista , primer controlador de vista y segundo controlador de vista.

Ahora quiero agregar el controlador secondview al controlador first view con animación. Lo he logrado, ahora tengo el controlador secondview transparente, por lo que podemos ver el controlador first view debajo del controlador secondview.

Pero no soy capaz de ver primero ver controlador, y solo puedo ver la pantalla negra debajo del controlador secondview.

Aquí está el código.

-(void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext{
    self.transitionContext = transitionContext;
    if(self.isPresenting){
        [self executePresentationAnimation:transitionContext];
    }
    else{
       [self executeDismissalAnimation:transitionContext];
    }
  }

-(void)executePresentationAnimation:(id<UIViewControllerContextTransitioning>)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 se llama [self.transitionContext completeTransition:YES];, de repente el primer controlador de vista desaparece y la pantalla negra aparece debajo del segundo controlador de vista.

¿Alguien tiene idea ? Gracias.

Author: mattjgalloway, 2014-06-21

15 answers

Yo estaba teniendo el mismo problema aquí – parece un error en iOS 8. Hepresentado un radar .

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

Revelan d

He jugado un poco y parece que hay una solución fácil, para casos simples. Solo puede volver a agregar la vista toViewController como una vista secundaria de la ventana de teclas:

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

He comprobado y la ventana de teclas rootViewController todavía está configurado correctamente, así que está bien. No estoy seguro de lo que pasaría si presentas tu controlador desde dentro de un controlador modal ya presentado, por lo que para casos más complejos, tendrás que experimentar.

 94
Author: Ash Furrow,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-07-05 18:41:14

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

La vista desaparece porque saca la vista del controlador de vista que se presenta de su ubicación original (jerarquía de vistas), la coloca dentro de la vista de contenedores que proporciona su animador, pero nunca la devuelve después de que la animación haya terminado. Para que la vista del controlador de vista se elimine completamente con su superview (containerView) de la ventana.

En iOS 7 el sistema siempre devuelve la vista las vistas de los controladores que están involucradas en la presentación (presenting y presented) a sus lugares originales después de que la transición haya terminado de animarse 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 vista que se presenta si la vista de ese controlador de vista va a estar oculta (eliminada de la jerarquía de vistas) completamente al final de la transición . En otras palabras, significa que una vez finalizada la animación de presentación inicial, solo será visible la vista del controlador de vista presentado y no la vista del controlador de vista que se presenta. Por ejemplo, si establece la opacidad de la vista del controlador de vista presentado en 50% y usa UIModalPresentationFullScreen, no podrá ver la vista de presentación del controlador de vista debajo de la vista presentada, pero si usa UIModalPresentationOverFullscreen - lo hará (el método shouldRemovePresentersView de UIPresentationController es responsable de especificar que).

¿Por qué no permitir que el animador manipule la vista del controlador de vista de presentación en todo momento? En primer lugar, si la vista del controlador de 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 es necesario animarla en absoluto, solo permanece donde está. En segundo lugar, 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 diseñar eso ver la vista del controlador cuando sea necesario, por ejemplo, cuando la orientación cambia, pero el propietario original del controlador de vista que se presenta lo hace.

En iOS 8 viewForKey: se introdujo el método para obtener vistas que el animador manipula. En primer lugar, es útil seguir la regla descrita anteriormente devolviendo nil siempre que el animador no toque la vista. En segundo lugar, puede devolver una vista diferente para que el animador la anime. Imagine que está implementando una presentación similar a form hoja. En este caso, debería agregar alguna sombra o decoración alrededor de la vista del controlador de vista presentado. El animador animará esa decoración en su lugar y la vista del controlador de vista presentado será una hija de la decoración.

viewControllerForKey: no desaparece, todavía se puede utilizar si se necesita un acceso directo a los controladores de vista, pero el animador no debe hacer ninguna suposición sobre las vistas que necesita animar.

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

  1. Si no necesita animar la vista del controlador de vista que se presenta, use viewForKey: para obtener vistas para animar en lugar de llegar a 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 que se presentan, debe considere usar UIModalPresentationFullScreen style o continúe usando UIModalPresentationCustom e implemente su propia subclase de UIPresentationController con shouldRemovePresentersView devolviendo YES. De hecho, la implementación de este método es la principal diferencia entre los controladores de presentación internos definidos por los estilos UIModalPresentationFullScreen y UIModalPresentationCustom, aparte del hecho de que este último le permite usar controladores de presentación personalizados.

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

 69
Author: egdmitry,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-03-14 23:16:13

En iOS 8, debe manipular las vistas devueltas por viewForKey: en lugar de la propiedad .view de los controladores de vista devueltos por viewControllerForKey:. Esto no está particularmente claro en la documentación beta, pero si miras el código fuente de UIViewControllerTransitioning.h verás 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;

Así que en lugar de ajustar fotogramas etc de toViewController.view, use el valor devuelto de [transitionContext viewForKey:UITransitionContextToViewKey].

Si su aplicación necesita admitir iOS7 y / o Xcode 5, puede usar una categoría simple método en UIViewController como el siguiente:

- (UIView *)viewForTransitionContext:(id<UIViewControllerContextTransitioning>)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, obtenga su toViewController y fromViewController como de costumbre, pero obtenga las vistas usando [toViewController viewForTransitionContext:transitionContext].

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

Edición 2: Gracias a graveley por la sugerencia, el uso de UIModalPresentationFullScreen soluciona el problema. Tal vez esto no es un error. Apple puede intentar que UIModalPresentationCustom solo modifique la vista del modal entrante. Si desea modificar la vista saliente, ¿necesita garantizar la presentación a pantalla completa de la nueva vista? En cualquier caso, debe usar viewForKey y UIModalPresentationFullScreen.

 68
Author: bcherry,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-09-11 21:56:41

No establecer modalPresentationStyle a UIModalPresentationCustom solucionó el problema para mí.

En otras palabras, dejar por defecto UIModalPresentationFullScreen en lugar de especificar UIModalPresentationCustom solucionó el problema de desaparición de la vista. Tenga en cuenta que el protocolo UIViewControllerTransitioningDelegate todavía parece ser seguido incluso cuando se deja esto por defecto. Si mal no recuerdo, érase una vez UIModalPresentationCustom era un requisito.

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

 22
Author: graveley,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-09-06 20:17:12

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

Para resumirlo:

  1. establezca modalPresentationStyle en .Custom
  2. subclase UIPresentationController, override shouldRemovePresentersView (with NO)
  3. reemplazar presentationControllerForPresentedviewcontroller en su TransitionDelegate de la clase y retorno personalizado UIPresentationController

+ 1 en su costumbre transición, no agregue toView cuando la animación de despido está sucediendo.

Demostrado aquí:

Https://www.dropbox.com/s/7rpkyamv9k9j18v/CustomModalTransition.zip?dl=0 sin hacks! es como magia! :)

 13
Author: Mark Aron Szulyovszky,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:26:19

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

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

Le pide a su delegado el controlador de presentación personalizado para administrar la jerarquía de vistas al presentar un controlador de vistas.

Valor de retorno:

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

Discusión:

Cuando presenta un controlador de vista usando el UIModalPresentationCustom estilo de presentación, el sistema llama a esto método y pide el controlador de presentación que administra su estilo personalizado. Si implementa este método, utilícelo para crear y devuelve el objeto controlador de presentación personalizado que desea utilizar gestionar el proceso de presentación.

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

Disponibilidad Disponible en iOS 8.0 y versiones posteriores.

Para obtener 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 del WWDC llamado "LookInside: Adaptabilidad de los Controladores de presentación y Objetos de Animador personalizados", que puede descargar del código de muestra del WWDC 2014 pagina.

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

initWithPresentedViewController:presented presentingViewController:presenting

Antes se presentaba y luego se presentaba. Solo cámbialos y debería funcionar.

 8
Author: Paulo Faria,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-09-10 21:56:08

En lugar de [InView insertSubview:toViewController.ver aboveSubview: fromViewController.vista]; solo tienes que añadir: [InView addSubview: toViewController.vista];

if (self.presenting) {

    [transitionContext.containerView addSubview:toViewController.view];
    // your code

} else {
    // your code
}

Puedes ver un ejemplo aquí: link y funciona en iOS 7 y iOS 8

 7
Author: CarlosGz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-07-10 18:38:30

Aquí hay una versión de Objective C de la correcció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 agregar la vista de nuevo para obtener la presentación de un nuevo controlador de vista desde el bloque de terminación de despido de otro controlador de vista para trabajar correctamente.

No se si esto lo arreglara para todos pero funciona en mi app. ¡Salud!

 7
Author: vichudson1,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-09-10 21:59:04

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.

 5
Author: Reedy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-09-16 12:14:10

Encontré que viewForKey:UITransitionContextToViewKey devuelve nil en ios8. Así que si es nil, agarro la vista desde el controlador de vista 'para'.

Sin embargo, esto parece dar lugar a que la vista 'to' no se mueva del contenedor a la ventana cuando se llama a completeTransition:YES. Así que si viewForKey:UITransitionContextToViewKey devuelve nil, caigo a toVC.view, y mantengo un registro del hecho de que devolvió nil, y después de la finalización lo muevo a la vista superior inicial del contenedor (que resulta ser la ventana).

Así que este código funciona tanto en iOS7 como en iOS8, y debería trabajar en iOS9 también, incluso si lo arreglan o no.

- (void)animateTransition:(id <UIViewControllerContextTransitioning>)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];
        }
    }];
}
 5
Author: Chris,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-04-17 05:49:58

He encontrado que este error (y muchos más!) desaparece si se establece modalPresentationStyle = UIModalPresentationFullScreen. Por supuesto, todavía obtiene su animación de transición personalizada.

 3
Author: Chris,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-05-15 05:20:46

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

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

Resulta que debe establecer el estilo de presentación ANTES DE QUE LA VISTA CARGUE. No después. Hazlo en init, o desde el controlador anterior, o como quieras , siempre y cuando sea antes de que se cargue la vista.

 2
Author: Jordan Smith,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-01-29 00:15:38

Usando el nuevo UIModalPresentationOverCurrentContext lo arreglé. Mi transición original en iOS 7 era solo para tener un fondo borroso de la vista debajo del modal.

 1
Author: malhal,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-12-14 21:45:06

Me quedé atascado 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 tenía 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 de Swift 3 de esa respuesta que he probado para iOS 9 e iOS 10:

  1. Crea una subclase de UIPresentationController. Override the shouldRemovePresentersView to false como sigue:

    class ModalPresentationController: UIPresentationController {
    
    override var shouldRemovePresentersView: Bool {
    return false
    }
    
    override func containerViewWillLayoutSubviews() {
    presentedView?.frame = frameOfPresentedViewInContainerView
    }
    }
    
  2. En el lugar donde está instanciando el nuevo controlador de vista y estableciendo 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 sobrescribe el método presentationController de su UIViewControllerTransitioningDelegate y devuelve 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 a tener en cuenta es que no deberías intentar hacer referencia a tu fromView en tu clase presentAnimator. Esto será nil y obtendrá un error en tiempo de ejecución. Aparte de eso, si implementas cosas como things, obtendrás tu transición personalizada con su animación y un fondo semitransparente si haces una.

 1
Author: gwinyai,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-12-24 15:07:06

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

[self addChildViewController:childViewController];                 

Compruebe y hágamelo saber.

 -2
Author: Rushabh,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-06-21 06:21:35