Domanda Soluzione alternativa per le animazioni personalizzate di UIViewController in orizzontale?


Ho una transizione UIViewController animata personalizzata e sembra che ci sia un bug in iOS che rovina il layout in orizzontale. Nel metodo di animazione principale, mi viene data una combinazione di viste di paesaggio e di ritratto. (Nel ritratto le viste sono tutte a ritratto, quindi nessun problema.)

- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext;
{
  UIViewController* toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
  UIViewController* fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
  UIView *containerView = [transitionContext containerView];

  // fromViewController.view => landscape, transform
  // toViewController.view => portrait, transform
  // containerView => portrait, no transform

  [containerView addSubview:toViewController.view];

  // ...animation... //
}

So che il frame la proprietà non è affidabile quando una vista ha una trasformazione, quindi suppongo che questa sia la radice del problema. In modalità orizzontale, le visualizzazioni di to / from viewController hanno una trasformazione in senso orario di 90 gradi [0 -1 1 0]. Ho provato ad usare i limiti / centro per ridimensionare e posizionare la vista, oltre a rimuovere la trasformazione e poi riapplicarla, ma Uikit mi combatte e insiste a visualizzare la vista come ritratto. Fastidioso!

Nello screenshot, il grigio scuro è lo sfondo di UIWindow e il rosso è il controller di visualizzazione modale aggiunto che dovrebbe coprire l'intero schermo.

the red view is in portrait 

Qualcuno ha trovato una soluzione?


16
2017-11-16 02:32


origine


risposte:


Ok, la correzione è sorprendentemente semplice:

Impostare il frame toViewController sul contenitore prima aggiungendo la vista al contenitore.

toViewController.view.frame = containerView.frame;
[containerView addSubview:toViewController.view];

Aggiornare: C'è ancora un limite nel fatto che non si conosce l'orientamento della cornice. Inizialmente è ritratto, ma allungato in orizzontale quando viene visualizzato sullo schermo. Se si desidera scorrere la vista da destra, in orizzontale potrebbe scorrere dalla "cima" (o in basso se si guarda l'altro paesaggio!)


15
2017-11-19 02:16



Mi sono imbattuto in questo problema e semplicemente non ritengo che le soluzioni di cui sopra facciano giustizia. Propongo una soluzione che non richiede codice hacky e frame hard coded.

UIView ha una funzione fantastica per convertire un CGRect nello spazio di coordinate di un altro (vale a dire; +[UIView convertRect:fromView:]). Quindi voglio dettagliare un modo molto più semplice per ottenere questo effetto in qualsiasi orientamento senza valori hardcoded. In questo esempio, diciamo che vogliamo una semplice animazione che scorri una vista dalla destra dello schermo.

Quindi nel nostro animatore animateTransition(:) potremmo semplicemente eseguire quanto segue:

veloce

func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
    let toViewController = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)!
    let fromViewController = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)!

    let toView = toViewController.view
    let fromView = fromViewController.view
    let containerView = transitionContext.containerView()

    if(isPresenting) {
        //now we want to slide in from the right
        let startingRect = CGRectOffset(fromView.bounds, CGRectGetWidth(fromView.bounds), 0)
        toView.frame = containerView.convertRect(startingRect, fromView:fromView);
        containerView.addSubview(toView)
        let destinationRect = containerView.convertRect(fromView.bounds, fromView: fromView)
        UIView.animateWithDuration(transitionDuration(transitionContext),
            delay: 0,
            usingSpringWithDamping: 0.7,
            initialSpringVelocity: 0.7,
            options: .BeginFromCurrentState,
            animations: { () -> Void in
                toView.frame = destinationRect
        }, completion: { (complete) -> Void in
            transitionContext.completeTransition(!transitionContext.transitionWasCancelled())
        })
    } else {
        //we want to slide out to the right
        let endingRect = containerView.convertRect(CGRectOffset(fromView.bounds, CGRectGetWidth(fromView.bounds), 0), fromView: fromView)
        UIView.animateWithDuration(transitionDuration(transitionContext),
            delay: 0,
            usingSpringWithDamping: 0.7,
            initialSpringVelocity: 0.7,
            options: .BeginFromCurrentState,
            animations: { () -> Void in
                fromView.frame = endingRect
            }, completion: { (complete) -> Void in
                if !transitionContext.transitionWasCancelled() {
                    fromView.removeFromSuperview()
                }
                transitionContext.completeTransition(!transitionContext.transitionWasCancelled())
        })
    }
}

Objective-C

UIViewController* toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIViewController* fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];

UIView *toView = toViewController.view;
UIView *fromView = fromViewController.view;
UIView *containerView = [transitionContext containerView];

if(self.isPresenting) {
    //now we want to slide in from the right
    CGRect startingRect = CGRectOffset(fromView.bounds, CGRectGetWidth(fromView.bounds), 0);
    toView.frame = [containerView convertRect:startingRect fromView:fromView];
    [containerView addSubview:toView];
    [UIView animateWithDuration:[self transitionDuration:transitionContext]
                     animations:^{
                         toView.frame = [containerView convertRect:fromView.bounds
                                                          fromView:fromView];
                     }
                     completion:^(BOOL finished) {
                         [transitionContext completeTransition:![transitionContext transitionWasCancelled]];
                     }];

} else {
    //we want to slide out to the right
    [UIView animateWithDuration:[self transitionDuration:transitionContext]
                     animations:^{
                         CGRect endingRect = CGRectOffset(fromView.bounds, CGRectGetWidth(fromView.bounds), 0);
                         fromView.frame = [containerView convertRect:endingRect fromView:fromView];
                     }
                     completion:^(BOOL finished) {
                         [fromView removeFromSuperview];
                         [transitionContext completeTransition:![transitionContext transitionWasCancelled]];
                     }];

}

Spero che questo aiuti qualcun altro che è venuto qui nella stessa barca (se lo fa, un up-voto non farà male :))


13
2017-07-17 09:56



La risposta esistente va in parte ma non fino in fondo (vogliamo una corretta gestione dei frame e delle rotazioni su entrambi i dispositivi, tutti gli orientamenti, sia per le transizioni animate che interattive).

Questo post del blog aiuta:

http://www.brightec.co.uk/blog/ios-7-custom-view-controller-transitions-and-rotation-making-it-all-work

E cita una persona di supporto Apple affermando la vera natura del problema:

"Per le transizioni di presentazione personalizzate impostiamo una vista intermedia tra la finestra e la vista di rootViewController di Windows.Questa vista è il contenitoreVisualizza in cui si esegue l'animazione. A causa di un dettaglio di implementazione della rotazione automatica su iOS, quando l'interfaccia ruota applichiamo un trasformazione affine alla vista di rootViewController di Windows e modificare di conseguenza i suoi limiti Poiché il contenitoreView eredita le sue dimensioni dalla finestra invece della vista del controller della vista radice, è sempre orientato in verticale. "

"Se l'animazione della presentazione dipende dall'orientamento del controller della vista di presentazione, sarà necessario rilevare l'orientamento del controller della vista che presenta e modificare opportunamente l'animazione.Il sistema applicherà la trasformazione corretta al controller della vista in entrata, ma è necessario che l'animatore configurare il frame del controller della vista in entrata. "

Ma non affronta le transizioni interattive.

Ho trovato una soluzione completa al problema qui: 

https://github.com/alfiehanssen/Cards

In sostanza, è necessario calcolare i frame dei viewControllers in base all'orientamento di uno dei viewControllers (toViewController o fromViewController) anziché dei limiti del containerView di transitionContext.


8
2018-03-26 17:26



Sono stato perplesso anche con questo problema. Non mi piaceva troppo la soluzione switch / case. Alla fine ho creato questa funzione:

@implementation UIView (Extras)

- (CGRect)orientationCorrectedRect:(CGRect)rect {
    CGAffineTransform ct = self.transform;

    if (!CGAffineTransformIsIdentity(ct)) {
        CGRect superFrame = self.superview.frame;
        CGPoint transOrigin = rect.origin;
        transOrigin = CGPointApplyAffineTransform(transOrigin, ct);

        rect.origin = CGPointZero;
        rect = CGRectApplyAffineTransform(rect, ct);

        if (rect.origin.x < 0.0) {
            transOrigin.x = superFrame.size.width + rect.origin.x + transOrigin.x;
        }
        if (rect.origin.y < 0.0) {
            transOrigin.y = superFrame.size.height + rect.origin.y + transOrigin.y;
        }
        rect.origin = transOrigin;
    }

    return rect;
}

- (CGRect)orientationCorrectedRectInvert:(CGRect)rect {
    CGAffineTransform ct = self.transform;

    if (!CGAffineTransformIsIdentity(ct)) {
        ct = CGAffineTransformInvert(ct);

        CGRect superFrame = self.superview.frame;
        superFrame = CGRectApplyAffineTransform(superFrame, ct);
        CGPoint transOrigin = rect.origin;
        transOrigin = CGPointApplyAffineTransform(transOrigin, ct);

        rect.origin = CGPointZero;
        rect = CGRectApplyAffineTransform(rect, ct);

        if (rect.origin.x < 0.0) {
            transOrigin.x = superFrame.size.width + rect.origin.x + transOrigin.x;
        }
        if (rect.origin.y < 0.0) {
            transOrigin.y = superFrame.size.height + rect.origin.y + transOrigin.y;
        }

            rect.origin = transOrigin;
        }

        return rect;
    }

Fondamentalmente, puoi creare i tuoi frame rects usando le coordinate portrait o landscape, ma eseguilo attraverso la funzione con la trasformazione della vista prima di applicarlo alla vista. Con questo metodo, è possibile utilizzare i limiti per ottenere la dimensione della vista corretta.

        CGRect endFrame = toViewController.view.frame;
        CGRect startFrame = endFrame;
        startFrame.origin.y = fromViewController.view.bounds.size.height;

        endFrame = [fromViewController.view orientationCorrectedRect:endFrame];
        startFrame = [fromViewController.view orientationCorrectedRect:startFrame];

        toViewController.view.frame = startFrame;

3
2018-04-15 11:21



Una soluzione consiste nell'avere una transizione molto breve (o zero secondi), quindi una volta terminata la transizione e presentato il controller della vista, verranno applicate le trasformazioni corrette. Quindi esegui le animazioni dall'interno del controller di visualizzazione presentato.


-1
2018-05-05 23:39