1

I want build interactive transition with UIViewAnimation.But there's few layer property that I can animate it.So i decided use the CAAnimation. I wanna change the ViewController's view mask,Here is the code

-(NSTimeInterval)transitionDuration:(nullable id<UIViewControllerContextTransitioning>)transitionContext{
    return 0.5f;
}


-(void)animateTransition:(nonnull id<UIViewControllerContextTransitioning>)transitionContext{

   _transitionContext=transitionContext;
   UIViewController *fromVC=
   [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
   UIViewController *toVC=
   [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
   UIView *containerView=[transitionContext containerView];

   _toVC=toVC;
   _fromVC=fromVC;
   [containerView insertSubview:toVC.view aboveSubview:fromVC.view];

   //Create the BezierPath
   UIBezierPath *initailPath=[UIBezierPath bezierPathWithRect:(CGRect)  {{_cellRect.size.width/2,_cellRect.origin.y+_cellRect.size.height/2},.size= {0.5,0.5}}];
   CGFloat radius;
   CGFloat distance;
   if (fromVC.view.frame.size.width>fromVC.view.frame.size.height) {
       distance=fromVC.view.frame.size.width-_cellRect.origin.x;
       radius=distance>_cellRect.origin.x?distance:_cellRect.origin.x+88;
   }else{
       distance=fromVC.view.frame.size.height-_cellRect.origin.y;
       radius=distance>_cellRect.origin.y?distance:_cellRect.origin.y+88;
   }
   radius=radius*2;
   UIBezierPath *finalPath=[UIBezierPath   bezierPathWithOvalInRect:CGRectInset(_cellRect,
                                                                           -  radius,
                                                                           - radius)];
   _initaialPath=initailPath;
   _finalPath=finalPath;
   //Create a Layer Mask
   _maskLayer=[[CAShapeLayer alloc] init];
   _maskLayer.path=finalPath.CGPath;
   toVC.view.layer.mask=_maskLayer;


   [self animateLayer:_maskLayer withCompletion:^{

       BOOL isComple=![transitionContext transitionWasCancelled];
       if (!isComple) {

           [containerView addSubview:fromVC.view];
           [toVC.view removeFromSuperview];
       }
       [transitionContext completeTransition:isComple];
   }];
  }
-(void)startInteractiveTransition:(nonnull id<UIViewControllerContextTransitioning>)transitionContext{

   _transitionContext=transitionContext;
   [self animateTransition:transitionContext];
   [self pauseTime:[_transitionContext containerView].layer];

}

This is the main animation,it work perfectly without interactive. Then i tried to control it with these code:

-(void)updateInteractiveTransition:(CGFloat)percentComplete{
   [_transitionContext updateInteractiveTransition:percentComplete];
   [_transitionContext containerView].layer.timeOffset=_pausedTime + [self transitionDuration:_transitionContext]*percentComplete;
}

-(void)finishInteractiveTransition{

   [_transitionContext finishInteractiveTransition];
   [self resumeTime:[_transitionContext containerView].layer];
}

These two functions work perfectly

Buthere is the Problem with "Cancel Transition" When i cancel the transition it disappear suddenly (I have tried with the solution in this question But That's not work for me) here is now my code:

- (void)cancelInteractiveTransition {
   //Must Cancel System InteractiveTransition FRIST
   [_transitionContext cancelInteractiveTransition];
   //Then adjust the layer time
   CALayer *maskLayer =[_transitionContext containerView].layer;
   maskLayer.beginTime = CACurrentMediaTime();
   //MOST IMPORTANT
   [UIView animateWithDuration:0.5 animations:^{

       maskLayer.timeOffset=0.0;

   } completion:^(BOOL finished) {

       [[_transitionContext containerView ] addSubview:_fromVC.view];
       [_toVC.view removeFromSuperview];
   }];

}

Now I almost spend whole week on this, If you can help me I really predicate that.

Community
  • 1
  • 1
HHD
  • 49
  • 8
  • The issue is that you cannot use UIView animate* with CAAnimations. I'm also looking for a solution to this exact issue (animate on cancel back to the start state, with masks involved) – xaphod Jul 23 '15 at 13:55
  • I got the solution @xaphod, I will answer the question. – HHD Jul 25 '15 at 04:18

1 Answers1

3

We can easily animate the layer's property from the initial value to the final value. Generally when we want implement a interactive animation, we can just use the UIView's animation block to reach this(Go to final position or back to original position). But you can not do this with some property like BackgroundColour,CGPath.

Acturlly we can control the process by using CAAnimation, and control the timeoffset of CALayer. When we control the animation process in some precise position. how can we make it back to the animation process? If the property you controlled is position.

    [UIView animateWithDuration:/*Left time*/ 
                          delay:/*delay time*/          
         usingSpringWithDamping:/*damping*/ 
          initialSpringVelocity:/*speed*/ 
                        options:/*option*/ 
                     animations:^{

                        /*Your animation*/

                   } completion:^(BOOL finished) {

    }];

But it's not suitable for something like CGPath or BackgroundColor.

We need a way to drive the animation, apple have supported a Driver, this driver will call a function,that you specified.And it will perform it 60times per second just like iPhone screen. The tech is CADisplayLink object. It is a timer object that allows your application to synchronise your drawing to the refresh rate of the display.

Then I got the solution:

if (!_displayLink) {
    _displayLink=[CADisplayLink displayLinkWithTarget:self selector:@selector(animationTick:)];
    [_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
}

AnimationTick Function is your function to refresh:

-(void)animationTick:(CADisplayLink *)displayLink{

  CALayer *maskLayer=[_transitionContext containerView].layer;
  CGFloat timeOffset=maskLayer.timeOffset;
  timeOffset=MAX(0,timeOffset-_piceDistance);
  maskLayer.timeOffset=timeOffset;
  if (timeOffset==0) {
      displayLink.paused=YES;
  }
}

That's all

HHD
  • 49
  • 8