AWPercentDrivenInteractiveTransition icon indicating copy to clipboard operation
AWPercentDrivenInteractiveTransition copied to clipboard

Flashing when canceling animation

Open ilant opened this issue 9 years ago • 16 comments

flashing (flickering) occurs when canceling animation (returning to current view controller), something like showing next view controller for milliseconds - and then hide it

How it can be prevented?

thaks

ilant avatar Nov 25 '14 14:11 ilant

This only happens in the simulator, never on a real device, at least for me.

MrAlek avatar Dec 14 '14 13:12 MrAlek

Closing issue since I can't reproduce it and haven't got more reports on it.

MrAlek avatar Jan 31 '15 16:01 MrAlek

Hi Alek, I'm experiencing this issue as well. If I put a breakpoint before [transitionContext completeTransition:] I see the toView momentarily before it vanishes.

EDIT: happening on my iPhone 6

BodaciousPie avatar Feb 10 '15 12:02 BodaciousPie

Wierd, I'll have a look at it. Thanks for the report!

MrAlek avatar Feb 10 '15 13:02 MrAlek

Hi! I'm experiencing this issue too. It's reproduce even in your example if you will start dragging in some way (for example, left to right) and then you will try to drag to other way (from right to left). And when you will release finger (cancel will call) - flickering appear.

Also it can be reproduced by starting dragging, then hold for some time (about 1 second or more) and then release finger.

Antowkos avatar Jun 09 '15 11:06 Antowkos

Yeah this issue is definitely still present in iOS 9. See comments on pull https://github.com/MrAlek/AWPercentDrivenInteractiveTransition/pull/5.

MrAlek avatar Sep 20 '15 20:09 MrAlek

I'm experiencing this problem as well. Pull Request #5 looks like a feasible solution.

Although note that, related to this, probably all this layer timing handling code should be done from the context itself instead of from the *PercentDrivenInteractiveTransition. See this documentation snippet from UIPercentDrivenInteractiveTransition's - updateInteractiveTransition: documentation:

This is a convenience method that calls through to the updateInteractiveTransition: method of the context object.

And the documentation on the same method on the UIViewControllerContextTransitioning protocol:

While tracking user events, gesture recognizers or your interactive animator objects should call this method regularly to update the progress toward completing the transition. If, during tracking, the interactions cross a threshold that you consider signifies the completion or cancellation of the transition, stop tracking events and call the finishInteractiveTransition or cancelInteractiveTransition method.

rsanchezsaez avatar Sep 28 '15 21:09 rsanchezsaez

As the comment I've written on #5 implies, I don't think it's a good enough solution.

I would much rather have the underlying problem fixed which is that UIView animations set their model properties on completion (see http://stackoverflow.com/questions/5040494/objective-c-ios-development-animations-autoreverse for instance). One could try to capture the incoming animation or introspect the CAPropertyAnimation objects that get created and reverse the model properties.

MrAlek avatar Sep 29 '15 18:09 MrAlek

And regarding the second comment, I agree with you in some sense. It should actually be the UIViewControllerTransitionCoordinator's job. Especially if you have other animations going alongside the transition.

The transition coordinator works with the animator objects to ensure that any extra animations are performed in the same animation group as the transition animations. Having the animations in the same group means that they execute at the same time and can all respond to timing changes provided by an interactive animator object. These timing adjustments happen automatically and do not require any extra code on your part.

The transition coordinator might also be involved in the screen flashing issue:

In addition to registering animations to perform during the transition, you can call the notifyWhenInteractionEndsUsingBlock: method to register a block to clean up animations associated with an interactive transition. Cleaning up is particularly important when the user cancels a transition interactively. When a cancellation occurs, you need to return to the original view hierarchy state as it existed before the transition.

I've spoken with Apple engineers about this and since the entire view controller transitioning mechanism was never meant to be used with custom container view controllers everything is a hack.

From the UIViewControllerContextTransitioning documentation:

Do not adopt this protocol in your own classes, nor should you directly create objects that adopt this protocol.

MrAlek avatar Sep 29 '15 18:09 MrAlek

Hello MrAlek. I too have hit up against this cancel-flash issue. Most reliably on a device (iphone 5s / ios9). Less reliably on the simulator, and not at all on a low-powered ipad mini v1. When it is evident, the affect is so noticable it makes this whole setup unusable.

The fix I have come up with seems to work, but it feels like a hack-on-top-of-a-hack. I have implemented it in the context of your container view controller interactive transition project.

1/ add a public property to your transitionContext object:

@property (nonatomic, strong) UIView* snapshotView;

2/ Create a snapshot of the fromView in the transitiionContext init method and add as a subView to the toView:

self.snapshotView = [fromViewController.view snapshotViewAfterScreenUpdates:NO];
[toViewController.view addSubview:self.snapshotView];
self.snapshotView.alpha = 0.0;

3/ In AWPercentDrivenIntercativeTranstion, set the snapshot alpha to 1.0 in - _transitionFinished if transitionWasCancelled is YES

   SEL selector = NSSelectorFromString(@"snapshotView");
    if ([_transitionContext respondsToSelector:selector]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
        [[_transitionContext performSelector:selector] setAlpha:1.0];
#pragma clang diagnostic pop

4/ Remove the snapshot in the transtionContext's -completeTranstion method:

- (void)completeTransition:(BOOL)didComplete {
    [self.snapshotView removeFromSuperview];
    if (self.completionBlock) {
        self.completionBlock (didComplete);
    }
}

Here is a link to my fork of your custom container transitions project with suggested fix. It would be better if we could remove that code from AWPercentDrivenInteractiveTransition and manage everything from inside the transitionContext, but that seems to be the precise point at which you need to show the shapshot. Any earlier, and you will see it during the interactive transition (the toView will mirror the fromView). Any later, and you are vulnerable to the flash.

This approach falls down completely when using a transitionCoordinator with animateAlongside - the alongside-animation is prone to the same flash issue - but it's the best I can come up with so far.

foundry avatar Sep 30 '15 14:09 foundry

My 5 cents:

while playing with https://github.com/MrAlek/custom-container-transitions to suit it to my needs (vertical transitions and different animations), what helped me to fix flickering was adding

[[self viewControllerForKey:UITransitionContextToViewControllerKey].view removeFromSuperview];

to the end of -cancelInteractiveTransition method of PrivateTransitionContext class.

kambala-decapitator avatar Sep 30 '15 17:09 kambala-decapitator

Removing the toViewController's view doesn't work in the general case - it just means you get a black-blink instead a (image-of-the-toView) blink. Thats why I cover the toView with a snapshot of the fromView. It still blinks, but the blink should be identical in appearance to the non-blinking state.

foundry avatar Sep 30 '15 17:09 foundry

Thanks, foundry.

Your patch works for me.

angelmic avatar Jul 28 '16 02:07 angelmic

I have this working, I'd be curious if it works for others or what you think, I guess it's whether or not this feels more or less like a worse hack than anything else 😉 :

- (void)_transitionFinished {
    [_displayLink invalidate];

    CALayer *layer = [_transitionContext containerView].layer;
    layer.speed = 1;

    if ([_transitionContext transitionWasCancelled]) {
        [CATransaction begin];
        [self _cancelBasicAnimationsInLayer:layer];
        [CATransaction commit];
    } else {
        CFTimeInterval pausedTime = [layer timeOffset];
        layer.timeOffset = 0.0;
        layer.beginTime = 0.0; // Need to reset to 0 to avoid flickering :S
        CFTimeInterval timeSincePause = [layer convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime;
        layer.beginTime = timeSincePause;
    }
}

- (void)_cancelBasicAnimationsInLayer:(CALayer *)layer {
    for (CALayer *sublayer in layer.sublayers) {
        [self _cancelBasicAnimationsInLayer:sublayer];
    }
    for (NSString *key in layer.animationKeys) {
        CAAnimation *animation = [layer animationForKey:key];
        if ([animation isKindOfClass:[CABasicAnimation class]]) {
            CABasicAnimation *basicAnim = (CABasicAnimation *)animation;
            [layer setValue:basicAnim.toValue forKeyPath:basicAnim.keyPath];
        }
    }
}

drkibitz avatar May 16 '17 19:05 drkibitz

I've tested that solution limitedly, but it seems to work great in 100% of all case I've had thus far. Makes this solution totally usable again, thanks!

drkibitz avatar May 16 '17 19:05 drkibitz

FWIW: I get the same flickering when using the standard UIPercentDrivenInteractiveTransition.

deberle avatar Jul 05 '18 16:07 deberle