English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

Detailed Explanation of How to Customize Controller Transition Animation in iOS

Introduction

Recently, I have some free time, so I organized the projects I have done recently. This article mainly introduces the relevant content about iOS custom controller transition animation push, and shares it for everyone to refer and learn. There will be no more words, let's take a detailed look at the introduction together.

Illustration:


iOS7 Apple has launched the custom transition API. From then on, any animation that can be implemented with CoreAnimation can appear between the transitions of two ViewController. And the implementation is highly decoupled, which means that while ensuring the code is clean, if you want to replace other animation schemes, you only need to change a class name, truly experiencing the pleasure brought by high-quality code.

There are many tutorials on custom transition animations on the Internet, but I hope that the students can understand and get started easily.

There are two types of transitions: Push and Modal, so there are also two types of custom transition animations. Today we will talk about Push

custom transition animation Push

Firstly, set up the interface and add4number of buttons:

- (void)addButton{  
 self.buttonArr = [NSMutableArray array];  
 CGFloat margin =50;
 CGFloat width = (self.view.frame.size.width-margin*3)/2;
 CGFloat height = width;
 CGFloat x = 0;
 CGFloat y = 0;
 //columns
 NSInteger col = 2;
 for (NSInteger i = 0; i < 4; i ++) {   
  x = margin + (i%col)*(margin+width);
  y = margin + (i/col)*(margin+height) + 150;
  UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
  button.frame = CGRectMake(x, y, width, height);
  button.layer.cornerRadius = width * 0.5;
  [button addTarget:self action:@selector(btnclick:) forControlEvents:UIControlEventTouchUpInside];
  button.backgroundColor = [UIColor colorWithRed:arc4random()%255/255.0 green:arc4random()%255/255.0 blue:arc4random()%255/255.0 alpha:1.0];
  button.tag = i+1;
  [self.view addSubview:button];
  [self.buttonArr addObject:button];
 }
}

Add animation:

- (void)setupButtonAnimation{  
 [self.buttonArr enumerateObjectsUsingBlock:^(UIButton * _Nonnull button, NSUInteger idx, BOOL * _Nonnull stop) {   
  // positionAnimation
  CAKeyframeAnimation *positionAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
  positionAnimation.calculationMode = kCAAnimationPaced;
  positionAnimation.fillMode = kCAFillModeForwards;
  positionAnimation.repeatCount = MAXFLOAT;
  positionAnimation.autoreverses = YES;
  positionAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
  positionAnimation.duration = (idx == self.buttonArr.count - 1);63; 4 : 5+idx;
  UIBezierPath *positionPath = [UIBezierPath bezierPathWithOvalInRect:CGRectInset(button.frame, button.frame.size.width/2-5, button.frame.size.height/2-5);
  positionAnimation.path = positionPath.CGPath;
  [button.layer addAnimation:positionAnimation forKey:nil];   
  // scaleXAniamtion
  CAKeyframeAnimation *scaleXAniamtion = [CAKeyframeAnimation animationWithKeyPath:@"transform.scale.x"];
  scaleXAniamtion.values = @[@1.0, @1.1,@1.0];
  scaleXAniamtion.keyTimes = @[@0.0, @0.0, @5,@1.0];
  scaleXAniamtion.repeatCount = MAXFLOAT;
  scaleXAniamtion.autoreverses = YES;
  scaleXAniamtion.duration = 4+idx;
  [button.layer addAnimation:scaleXAniamtion forKey:nil];   
  // scaleYAniamtion
  CAKeyframeAnimation *scaleYAnimation = [CAKeyframeAnimation animationWithKeyPath:@"transform.scale.y"];
  scaleYAnimation.values = @[@1,@1.1,@1.0];
  scaleYAnimation.keyTimes = @[@0.0,@0.5,@1.0];
  scaleYAnimation.autoreverses = YES;
  scaleYAnimation.repeatCount = YES;
  scaleYAnimation.duration = 4+idx;
  [button.layer addAnimation:scaleYAnimation forKey:nil];   
 };
}

The interface is built:

Then, to implement a custom transition animation during Push, you need to comply with the protocol UINavigationControllerDelegate

Apple provides several protocol methods in UINavigationControllerDelegate, and their specific functions can be clearly understood by the return type.

//Used to customize the transition animation
- (nullable id)navigationController:(UINavigationController *)navigationController         animationControllerForOperation:(UINavigationControllerOperation)operation            fromViewController:(UIViewController *)fromVC             toViewController:(UIViewController *)toVC NS_AVAILABLE_IOS(7_0);
//Add user interaction to this animation
- (nullable id)navigationController:(UINavigationController *)navigationController interactionControllerForAnimationController:(id) animationController NS_AVAILABLE_IOS(7_0);

In the first method, just return an object that conforms to the UIViewControllerInteractiveTransitioning protocol and implement the animation within it.

  • Create an animation class that inherits from NSObject and declares UIViewControllerAnimatedTransitioning.
  • Override the protocol method in UIViewControllerAnimatedTransitioning.
//Return the animation time
- (NSTimeInterval)transitionDuration:(nullable id)transitionContext;
//Write the animation code inside it
- (void)animateTransition:(id)transitionContext;

Firstly, I define a class named LRTransitionPushController, which inherits from NSObject and conforms to the UIViewControllerAnimatedTransitioning protocol

 - (void)animateTransition:(id)transitionContext{  
 self.transitionContext = transitionContext;  
 //Get the source controller, do not write it as UITransitionContextFromViewKey
 LRTransitionPushController *fromVc = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
 //Get the target controller, do not write it as UITransitionContextToViewKey
 LRTransitionPopController *toVc = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];  
 //Get the container view
 UIView *containView = [transitionContext containerView];
 // Both are added to the container. Note the order, the view of the target controller needs to be added later
 [containView addSubview:fromVc.view];
 [containView addSubview:toVc.view];  
 UIButton *button = fromVc.button;
 //Draw the circle
 UIBezierPath *startPath = [UIBezierPath bezierPathWithOvalInRect:button.frame];
 //Create two instances of circular UIBezierPath; one is the size of the button, and the other has a radius sufficient to cover the screen. The final animation is performed between these two bezier paths
 //The point farthest from the screen center of the button
 CGPoint finalPoint;
 //Determine which quadrant the trigger point is in
 if(button.frame.origin.x > (toVc.view.bounds.size.width / 2)) {
  if (button.frame.origin.y < (toVc.view.bounds.size.height / 2)) {
   //First Quadrant
   finalPoint = CGPointMake(0, CGRectGetMaxY(toVc.view.frame));
  }
   //Fourth Quadrant
   finalPoint = CGPointMake(0, 0);
  }
 }
  if (button.frame.origin.y < (toVc.view.bounds.size.height / 2)) {
   //Second Quadrant
   finalPoint = CGPointMake(CGRectGetMaxX(toVc.view.frame), CGRectGetMaxY(toVc.view.frame));
  }
   //Third Quadrant
   finalPoint = CGPointMake(CGRectGetMaxX(toVc.view.frame), 0);
  }
 } 
 CGPoint startPoint = CGPointMake(button.center.x, button.center.y);
 //Calculate the radius of expansion outward = the distance from the button center to the farthest corner of the screen - Button Radius
 CGFloat radius = sqrt((finalPoint.x-startPoint.x) * (finalPoint.x-startPoint.x) + (finalPoint.y-startPoint.y) * (finalPoint.y-startPoint.y)) - sqrt(button.frame.size.width/2 * button.frame.size.width/2 + button.frame.size.height/2 * button.frame.size.height/2);
 UIBezierPath *endPath = [UIBezierPath bezierPathWithOvalInRect:CGRectInset(button.frame, -radius, -radius)];  
 //Assign the mask to the layer of the toVc view
 CAShapeLayer *maskLayer = [CAShapeLayer layer];
 maskLayer.path = endPath.CGPath;
 toVc.view.layer.mask = maskLayer;
 CABasicAnimation *maskAnimation =[CABasicAnimation animationWithKeyPath:@"path"];
 maskAnimation.fromValue = (__bridge id)startPath.CGPath;
 maskAnimation.toValue = (__bridge id)endPath.CGPath;
 maskAnimation.duration = [self transitionDuration:transitionContext];
 maskAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
 maskAnimation.delegate = self;
 [maskLayer addAnimation:maskAnimation forKey:@"path"]; 
}

Return the custom animation class that was just created within the method used to customize the transition animation in the controller.

- (id)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC{  
 if (operation == UINavigationControllerOperationPush) {
  return [LRTranstionAnimationPush new];
 }
  return nil;
 }
}

Up to this point, the custom transition animation is completed

The pop animation is just the reverse of the push animation, so it will not be elaborated here. If you have any questions, please refer to the code

Add sliding return gesture

As mentioned above, this method is to add user interaction to this animation, so we need to implement sliding return in the pop operation

The simplest way should be to use the UIPercentDrivenInteractiveTransition class provided by UIKit, which has already implemented the UIViewControllerInteractiveTransitioning protocol. Students can specify the completion percentage of the transition animation through the object of this class.

//Add user interaction to this animation
- (nullable id)navigationController:(UINavigationController *)navigationController interactionControllerForAnimationController:(id) animationController NS_AVAILABLE_IOS(7_0);

First step, add the gesture

 UIPanGestureRecognizer *gestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePan:)];
 [self.view addGestureRecognizer:gestureRecognizer];

Second step, determine the animation execution ratio through the change of user's sliding

- (void)handlePan:(UIPanGestureRecognizer *)gestureRecognizer {  
 /*By calling the updateInteractiveTransition: method of UIPercentDrivenInteractiveTransition, you can control the progress of the transition animation,
  When the user's pull-down gesture is completed, call finishInteractiveTransition or cancelInteractiveTransition, UIKit will automatically execute the remaining half of the animation,
  Or let the animation return to its initial state.*/   
 if ([gestureRecognizer translationInView:self.view].x>=0) {
  //Gesture slide ratio
  CGFloat per = [gestureRecognizer translationInView:self.view].x / (self.view.bounds.size.width);
  per = MIN(1.0, (MAX(0.0, per)));   
  if (gestureRecognizer.state == UIGestureRecognizerStateBegan) {    
   self.interactiveTransition = [UIPercentDrivenInteractiveTransition new];
   [self.navigationController popViewControllerAnimated:YES];    
  } else if (gestureRecognizer.state == UIGestureRecognizerStateChanged){    
   if([gestureRecognizer translationInView:self.view].x ==0){     
    [self.interactiveTransition updateInteractiveTransition:0.01;     
   }     
    [self.interactiveTransition updateInteractiveTransition:per];
   }    
  } else if (gestureRecognizer.state == UIGestureRecognizerStateEnded || gestureRecognizer.state == UIGestureRecognizerStateCancelled){    
   if([gestureRecognizer translationInView:self.view].x == 0){     
    [self.interactiveTransition cancelInteractiveTransition];
    self.interactiveTransition = nil;     
   }5) {     
    [ self.interactiveTransition finishInteractiveTransition];
   }
    [ self.interactiveTransition cancelInteractiveTransition];
   }
   self.interactiveTransition = nil;
  }     
 } else if (gestureRecognizer.state == UIGestureRecognizerStateChanged){
  [self.interactiveTransition updateInteractiveTransition:0.01;
  [self.interactiveTransition cancelInteractiveTransition]; 
 } else if ((gestureRecognizer.state == UIGestureRecognizerStateEnded || gestureRecognizer.state == UIGestureRecognizerStateCancelled)){{   
  self.interactiveTransition = nil;
 }  
}

Step 3: Return an instance of UIPercentDrivenInteractiveTransition in the delegate method for adding user interaction to the animation

- (id)navigationController:(UINavigationController *navigationController       interactionControllerForAnimationController:(id) animationController {
 return self.interactiveTransition;
}

If you find this article helpful, please like it with a click, thank you!

The code is placed inGitHubYou can download here, of course, you can alsoLocal Download

Summary

That's all for this article. I hope the content of this article is of certain reference value to everyone's learning or work. If you have any questions, you can leave a message for communication. Thank you for your support of the呐喊 tutorial.

Declaration: The content of this article is from the network, the copyright belongs to the original author. The content is contributed and uploaded by Internet users spontaneously. This website does not own the copyright, has not been manually edited, and does not assume any relevant legal liability. If you find any content suspected of copyright infringement, please send an email to: notice#oldtoolbag.com (Please replace # with @ when sending an email to report abuse, and provide relevant evidence. Once verified, this site will immediately delete the infringing content.)

You May Also Like