preface

Short step without thousands of miles, not small streams into rivers and seas. Learning is like rowing upstream; not to advance is to drop back. I’m a hauler who drifts from platform to platform. Nonsense not to say, directly to everyone dry goods, I hope to see you have a little help, excellent people have been praised.

figure

Here’s the picture. There’s a picture and there’s a truth.

Here I need to use the up slide implementation of tiktok in my last article

I recommend looking at ViewController transitions in iOS7 before reading this article

If the transition is not very understanding of the possible learning will have some difficulties and questions.

Transition call code

- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath { AwemeListViewController *awemeVC = [[AwemeListViewController alloc] init]; awemeVC.transitioningDelegate = self; //0 // 1 UICollectionViewCell *cell = [collectionView cellForItemAtIndexPath:indexPath]; // 2 CGRect cellFrame = cell.frame; // 3 CGRect cellConvertedFrame = [collectionView convertRect:cellFrame toView:collectionView.superview]; / / window pops transitions self. PresentScaleAnimation. CellConvertFrame = cellConvertedFrame; / / / / 4 disappear transitions self. DismissScaleAnimation. SelectCell = cell; // 5 self.dismissScaleAnimation.originCellFrame = cellFrame; //6 self.dismissScaleAnimation.finalCellFrame = cellConvertedFrame; //7 awemeVC.modalPresentationStyle = UIModalPresentationOverCurrentContext; //8 self.modalPresentationStyle = UIModalPresentationCurrentContext; //9 [self.leftDragInteractiveTransition wireToViewController:awemeVC]; [self presentViewController:awemeVC animated:YES completion:nil]; }Copy the code

View 2 takes out the frame coordinate of the current cell. 3 Converts the coordinate of the cell to the screen coordinate. 4 Sets the coordinate of the position of the cell on the screen when it pops up 6 set the original cell coordinate position of the disappear transition 7 set the final cell screen coordinate position for the disappear transition to complete the animation back to the original position 8 Set the pop-up VC pop-up style this is used to display the pop-up VC when the default bottom of the BluA gaussian blur 9 Sets the current VC’s modal pop-up style to the current pop-up context

The disappearing transition animation set in steps 5~7 will be explained below

Here we’re using the VC object that we talked about going up and down and you don’t have to worry about it if it’s just a normal UIViewController

The agent needed to implement the transfer

First in the need to implement UIViewControllerTransitioningDelegate this agent

天安门事件

#pragma mark - #pragma mark - UIViewControllerAnimatedTransitioning Delegate - (nullable id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source { return self.presentScaleAnimation; //present VC } - (nullable id <UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed { return  self.dismissScaleAnimation; //dismiss VC } - (nullable id <UIViewControllerInteractiveTransitioning>)interactionControllerForDismissal:(id <UIViewControllerAnimatedTransitioning>)animator { return self.leftDragInteractiveTransition.isInteracting? self.leftDragInteractiveTransition: nil; }Copy the code

And here we see that we are returning separately

  • Popup animation instance self. PresentScaleAnimation

  • Dismiss animation instance self. DismissScaleAnimation

  • And the self. LeftDragInteractiveTransition instance used for concrete implementation is responsible for switching transitions

    So we need to declare and initialize 3 member variables in the current VC

天安门事件

    @property (nonatomic, strong) PresentScaleAnimation *presentScaleAnimation;
    @property (nonatomic, strong) DismissScaleAnimation *dismissScaleAnimation;
    @property (nonatomic, strong) DragLeftInteractiveTransition *leftDragInteractiveTransition;
Copy the code

And initialize it in the viewDidLoad: method

天安门事件

/ / the two transitions animation self. PresentScaleAnimation = [[presentScaleAnimation alloc] init]; self.dismissScaleAnimation = [[DismissScaleAnimation alloc] init]; self.leftDragInteractiveTransition = [DragLeftInteractiveTransition new];Copy the code

Let me tell you what these three members are responsible for

First DragLeftInteractiveTransition class is responsible for transitions of gestures Process, that is, pan gesture to achieve in this class, and inherited from UIPercentDrivenInteractiveTransition class, this is after iOS7 ferry base class must be provided by the system in interactionControllerForDismissal: in the agency agreement Returns the instance of the class or subclass object, so we generate a member variable self. LeftDragInteractiveTransition

Then there are the animation classes that pop up present and dismiss, which are actually responsible for the animation after the simple gesture is done.

These two classes are inherited from the NSObject class and implement UIViewControllerAnimatedTransitioning agreement, this agreement there We need you to override some method to return the specific animation execution time, and the relevant container view and controller view instances we need in the middle, and then call the relevant block when we’re done telling us whether the transition is complete or not.

天安门事件

@implementation PresentScaleAnimation - (NSTimeInterval)transitionDuration:(id 0.3 f < UIViewControllerContextTransitioning >) transitionContext {return; } - (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext{ UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey]; if (CGRectEqualToRect(self.cellConvertFrame, CGRectZero)) { [transitionContext completeTransition:YES]; return; } CGRect initialFrame = self.cellConvertFrame; UIView *containerView = [transitionContext containerView]; [containerView addSubview:toVC.view]; CGRect finalFrame = [transitionContext finalFrameForViewController:toVC]; NSTimeInterval duration = [self transitionDuration:transitionContext]; toVC.view.center = CGPointMake(initialFrame.origin.x + initialFrame.size.width/2, initialFrame.origin.y + initialFrame.size.height/2); toVC.view.transform = CGAffineTransformMakeScale(initialFrame.size.width/finalFrame.size.width, initialFrame.size.height/finalFrame.size.height); [UIView animateWithDuration: duration delay: 0 usingSpringWithDamping: initialSpringVelocity 0.8:1 options:UIViewAnimationOptionLayoutSubviews animations:^{ toVC.view.center = CGPointMake(finalFrame.origin.x + finalFrame.size.width/2, finalFrame.origin.y + finalFrame.size.height/2); toVC.view.transform = CGAffineTransformMakeScale(1, 1); } completion:^(BOOL finished) { [transitionContext completeTransition:YES]; }]; } @endCopy the code

Very simple.

The vanishing animation is the same as above

天安门事件

@interface DismissScaleAnimation () @end @implementation DismissScaleAnimation - (instancetype)init { self = [super init]; if (self) { _centerFrame = CGRectMake((ScreenWidth - 5)/2, (ScreenHeight - 5)/2, 5, 5); } return self; } - (NSTimeInterval) transitionDuration: (id < UIViewControllerContextTransitioning >) transitionContext {return 0.25 f; } - (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext{ UIViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey]; // UINavigationController *toNavigation = (UINavigationController *)[transitionContext viewControllerForKey:UITransitionContextToViewControllerKey]; // UIViewController *toVC = [toNavigation viewControllers].firstObject; UIView *snapshotView; CGFloat scaleRatio; CGRect finalFrame = self.finalCellFrame; if(self.selectCell && ! CGRectEqualToRect(finalFrame, CGRectZero)) { snapshotView = [self.selectCell snapshotViewAfterScreenUpdates:NO]; scaleRatio = fromVC.view.frame.size.width/self.selectCell.frame.size.width; snapshotView.layer.zPosition = 20; }else { snapshotView = [fromVC.view snapshotViewAfterScreenUpdates:NO]; scaleRatio = fromVC.view.frame.size.width/ScreenWidth; finalFrame = _centerFrame; } UIView *containerView = [transitionContext containerView]; [containerView addSubview:snapshotView]; NSTimeInterval duration = [self transitionDuration:transitionContext]; FromVC. View. Alpha = 0.0 f; snapshotView.center = fromVC.view.center; snapshotView.transform = CGAffineTransformMakeScale(scaleRatio, scaleRatio); [UIView animateWithDuration: duration delay: 0 usingSpringWithDamping: initialSpringVelocity 0.8:0.2 Options: UIViewAnimationOptionCurveEaseInOut animations: ^ {snapshotView. Transform = CGAffineTransformMakeScale (1.0 f, 1.0 f); snapshotView. Frame = finalFrame;} completion: ^ (BOOL finished) {[transitionContext finishInteractiveTransition];  [transitionContext completeTransition:YES]; [snapshotView removeFromSuperview]; }]; } @endCopy the code

We need to say about the transitions transition mainly class DragLeftInteractiveTransition inherited from UIPercentDrivenInteractiveTransition transitions in process,

Declaration of header files

天安门事件

@interface DragLeftInteractiveTransition : UIPercentDrivenInteractiveTransition / * * are drag returned to identify whether we are using transitions interaction * / @ property (nonatomic, assign) BOOL isInteracting; /** set vc@param viewController instance to return */ -(void)wireToViewController:(UIViewController *)viewController; @endCopy the code

implementation

天安门事件

@interface DragLeftInteractiveTransition () @property (nonatomic, strong) UIViewController *presentingVC; @property (nonatomic, assign) CGPoint viewControllerCenter; @property (nonatomic, strong) CALayer *transitionMaskLayer; @ end @ implementation DragLeftInteractiveTransition # pragma mark - # pragma mark - override the methods copying method -(CGFloat)completionSpeed{ return 1 - self.percentComplete; } - (void)updateInteractiveTransition:(CGFloat)percentComplete { NSLog(@"%.2f",percentComplete); } - (void) cancelInteractiveTransition {NSLog (@ "transitions to cancel"); } - (void) finishInteractiveTransition {NSLog (@ "transitions to complete"); } - (CALayer *)transitionMaskLayer { if (_transitionMaskLayer == nil) { _transitionMaskLayer = [CALayer layer]; } return _transitionMaskLayer; } # pragma mark - # pragma mark - private methods private method - (void) prepareGestureRecognizerInView: (UIView *) view { UIPanGestureRecognizer *gesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handleGesture:)];  [view addGestureRecognizer:gesture]; } #pragma mark - #pragma mark - event response - (void)handleGesture:(UIPanGestureRecognizer *)gestureRecognizer { UIView *vcView = gestureRecognizer.view; CGPoint translation = [gestureRecognizer translationInView:vcView.superview]; if(! self.isInteracting && (translation.x < 0 || translation.y < 0 || translation.x < translation.y)) { return; {} switch (gestureRecognizer. State) case UIGestureRecognizerStateBegan: {/ / fix the bug when sliding from right to left to avoid beginning from sliding to the left again When did not start CGPoint vel = [gestureRecognizer velocityInView:gestureRecognizer.view]; if (! self.isInteracting && vel.x < 0) { self.isInteracting = NO; return; } self.transitionMaskLayer.frame = vcView.frame; self.transitionMaskLayer.opaque = NO; self.transitionMaskLayer.opacity = 1; self.transitionMaskLayer.backgroundColor = [UIColor whiteColor].CGColor; // The color must not be transparent [self.transitionMaskLayer setNeedsDisplay]; [self.transitionMaskLayer displayIfNeeded]; Self. TransitionMaskLayer. AnchorPoint = CGPointMake (0.5, 0.5); Self. TransitionMaskLayer. Position = CGPointMake (vcView. Frame. The size, width / 2.0 f, vcView. Frame. The size, height / 2.0 f); vcView.layer.mask = self.transitionMaskLayer; vcView.layer.masksToBounds = YES; self.isInteracting = YES; } break; case UIGestureRecognizerStateChanged: { CGFloat progress = translation.x / [UIScreen mainScreen].bounds.size.width; Progress = fMINF (fmaxF (progress, 0.0), 1.0); CGFloat ratio = 1.0f - progress*0.5f; [_presentingVC.view setCenter:CGPointMake(_viewControllerCenter.x + translation.x * ratio, _viewControllerCenter.y + translation.y * ratio)]; _presentingVC.view.transform = CGAffineTransformMakeScale(ratio, ratio); [self updateInteractiveTransition:progress]; break; } case UIGestureRecognizerStateCancelled: case UIGestureRecognizerStateEnded:{ CGFloat progress = translation.x / [UIScreen mainScreen].bounds.size.width; Progress = fMINF (fmaxF (progress, 0.0), 1.0); {if (progress < 0.2) [UIView animateWithDuration: progress delay: 0 options: UIViewAnimationOptionCurveEaseOut animations:^{ CGFloat w = [UIScreen mainScreen].bounds.size.width; CGFloat h = [UIScreen mainScreen].bounds.size.height;  [self.presentingVC.view setCenter:CGPointMake(w/2, H / 2)]; self. PresentingVC. The transform = CGAffineTransformMakeScale (1.0 f, 1.0 f);} completion: ^ (BOOL finished) {self. IsInteracting = NO; [self cancelInteractiveTransition];}]; }else { _isInteracting = NO; [self finishInteractiveTransition]; [_presentingVC dismissViewControllerAnimated:YES completion:nil]; } // Remove the mask [self.transitionMaskLayer removeFromSuperlayer]; self.transitionMaskLayer = nil; } break; default: break; }} #pragma mark - #pragma mark - public methods -(void)wireToViewController (UIViewController *)viewController { self.presentingVC = viewController; self.viewControllerCenter = viewController.view.center; [self prepareGestureRecognizerInView:viewController.view]; } @endCopy the code

We provide a wireToViewController externally: the method is used to create transitions externally.

We found a spot in the previous code

天安门事件

[self.leftDragInteractiveTransition wireToViewController:awemeVC];
[self presentViewController:awemeVC animated:YES completion:nil];
Copy the code

So what we need to do here is pass in the up and down VC instance that we’re going to pop up, and when we do that, give the VC self.view a pan gesture,

In the carbon method we can see the percentage related carbon of the start, end and completion process

天安门事件

#pragma mark - #pragma mark override methods -(CGFloat)completionSpeed{return 1 - self. PercentComplete; } - (void)updateInteractiveTransition:(CGFloat)percentComplete { NSLog(@"%.2f",percentComplete); } - (void) cancelInteractiveTransition {NSLog (@ "transitions to cancel"); } - (void) finishInteractiveTransition {NSLog (@ "transitions to complete"); }Copy the code

Check the following conditions before you go

天安门事件

UIView *vcView = gestureRecognizer.view; CGPoint translation = [gestureRecognizer translationInView:vcView.superview]; if(! self.isInteracting && (translation.x < 0 || translation.y < 0 || translation.x < translation.y)) { return; }Copy the code

Take out the view of gesture function, and then coordinate transformation, judge whether the current animation has started, if not, or x < y coordinate is to judge whether the current is beyond the boundary range and so on exception case processing.

You have to be careful at the beginning

天安门事件

/ / fix bugs when sliding from right to left to avoid beginning from sliding to the left again When did not start CGPoint vel = [gestureRecognizer velocityInView: gestureRecognizer. View]; if (! self.isInteracting && vel.x < 0) { self.isInteracting = NO; return; }Copy the code

Then start by adding a mask as view.mask to hide the tableView beyond its contentSize

The rest is the intermediate process

Key core code

This code pays attention to the most important things

[self updateInteractiveTransition:progress];
Copy the code

Update transition progress this is the class’s own method, call on the line

End of gesture

天安门事件

CGFloat progress = translation.x / [UIScreen mainScreen].bounds.size.width; Progress = fMINF (fmaxF (progress, 0.0), 1.0); {if (progress < 0.2) [UIView animateWithDuration: progress delay: 0 options: UIViewAnimationOptionCurveEaseOut animations:^{ CGFloat w = [UIScreen mainScreen].bounds.size.width; CGFloat h = [UIScreen mainScreen].bounds.size.height;  [self.presentingVC.view setCenter:CGPointMake(w/2, H / 2)]; self. PresentingVC. The transform = CGAffineTransformMakeScale (1.0 f, 1.0 f);} completion: ^ (BOOL finished) {self. IsInteracting = NO; [self cancelInteractiveTransition];}]; }else { _isInteracting = NO; [self finishInteractiveTransition]; [_presentingVC dismissViewControllerAnimated:YES completion:nil]; } // Remove the mask [self.transitionMaskLayer removeFromSuperlayer]; self.transitionMaskLayer = nil;Copy the code

This is set to a tolerance of 0.2 if you feel this should be open and the interface Settings can encapsulate themselves.

Remember call cancelInteractiveTransition method in case when the user canceled cancelled

Complete words call finishInteractiveTransition transitions

conclusion

The whole process is relatively simple. If you read the article of Meow God, you will understand more clearly the three processes of transition, which are the pop-up and disappear animation and an intermediate transition process that we need to be familiar with.

Optimization point: in the original open source project demo right slide is a bug, I did the following judgment

天安门事件

/ / fix bugs when sliding from right to left to avoid beginning from sliding to the left again When did not start CGPoint vel = [gestureRecognizer velocityInView: gestureRecognizer. View]; if (! self.isInteracting && vel.x < 0) { self.isInteracting = NO; return; }Copy the code

Vel is actually a variable that evaluates when we swipe in from the right. Fixed a bug in the original open source

In addition, the contentSize of the original open source tableView is exposed to the outside. I used a mask to hide the displayed area.

The only minor disappointment is the opacity gradient in Tiktok’s left slide back.

If you need tok Tok’s transition animation Demo, you can joinIOS Advanced Technology Exchange Group, get Demo, and more iOS learning materials

Reproduced: original address