效果图
代码
//
// LBTransition.m
// LBWaterFallLayout_Example
//
// Created by mac on 2024/6/16.
// Copyright © 2024 liuboliu. All rights reserved.
//
#import "LBTransition.h"
@interface LBPushAnimation:NSObject<UIViewControllerAnimatedTransitioning>
@property (nonatomic, weak) id <LBTransitionDelegate> delegate;
@end
@implementation LBPushAnimation
- (instancetype)initWithDelegate:(id <LBTransitionDelegate>) delegate
{
if (self = [super init]) {
self.delegate = delegate;
}
return self;
}
#pragma mark - UIViewControllerCanimatedtransitioning
- (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext{
return 0.25;
}
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
UIView *containerView = [transitionContext containerView];
UIViewController *toVC = (UIViewController *)[transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIView *toView = [transitionContext viewForKey:UITransitionContextToViewKey];
[containerView addSubview:toView];
UIViewController <LBTransitionDelegate> *transToVc;
if ([toVC conformsToProtocol:@protocol(LBTransitionDelegate)]) {
transToVc = (UIViewController <LBTransitionDelegate> *)toVC;
}
id transmitData = [self.delegate transmitViewData];
CGRect finalFrame = [transToVc transmitViewfinalFrameWithData:transmitData containerView:containerView];
UIView *transView = [self.delegate prepareTransimitView:containerView finalFrame:finalFrame];
toView.alpha = 0;
CGFloat scaleWidth = transView.frame.size.width / toView.frame.size.width;
CGFloat scaleHeight = transView.frame.size.height / toView.frame.size.height;
toView.transform = CGAffineTransformMakeScale(scaleWidth, scaleHeight);
toView.center = transView.center;
CGRect toViewFinalFrame = [transitionContext finalFrameForViewController:toVC];
CGPoint finalCenter = CGPointMake(toViewFinalFrame.origin.x + toViewFinalFrame.size.width * 0.5,
toViewFinalFrame.origin.y + toViewFinalFrame.size.height * 0.5);
NSTimeInterval duration = [self transitionDuration:transitionContext];
[UIView animateWithDuration:duration
delay:0
options:UIViewAnimationOptionCurveEaseInOut
animations:^{
[transToVc makeupAnimationStateTransmitView:transView
data:transmitData
containerView:containerView];
toView.alpha = 1;
toView.center = finalCenter;
toView.transform = CGAffineTransformIdentity;
} completion:^(BOOL finished) {
[self.delegate completeTransition];
[transToVc completeTransitionWithView:transView data:transmitData];
[transitionContext completeTransition:!transitionContext.transitionWasCancelled];
}];
}
@end
@interface LBPopAnimatin : NSObject <UIViewControllerAnimatedTransitioning>
@property (nonatomic, weak) id <LBTransitionDelegate> delegate;
@end
@implementation LBPopAnimatin
- (instancetype)initWithDelegate:(id <LBTransitionDelegate>)delegate {
if (self = [super init]) {
self.delegate = delegate;
}
return self;
}
#pragma mark - UIViewControllerAnimatedTransitioning
- (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext
{
return 0.25;
}
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
UIView *containerView = [transitionContext containerView];
UIView *toView = [transitionContext viewForKey:UITransitionContextToViewKey];
UIViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIView *fromView = fromVC.view;
[containerView insertSubview:toView aboveSubview:fromView];
UIViewController <LBTransitionDelegate> *transToVc;
if ([fromVC conformsToProtocol:@protocol(LBTransitionDelegate)]) {
transToVc = (UIViewController <LBTransitionDelegate> *)fromVC;
}
id transmitData = [transToVc transmitViewData];
CGRect finalFrame = [self.delegate transmitViewfinalFrameWithData:transmitData containerView:containerView];
UIView *transView = [transToVc prepareTransimitView:containerView finalFrame:finalFrame];
NSTimeInterval duration = [self transitionDuration:transitionContext];
CGPoint finalCenter = CGPointMake(finalFrame.origin.x + finalFrame.size.width * 0.5, finalFrame.origin.y + finalFrame.size.height * 0.5);
CGFloat scaleWidth = finalFrame.size.width / fromView.frame.size.width;
CGFloat scaleHeight = finalFrame.size.height / fromView.frame.size.height;
CGFloat scale = scaleWidth < scaleHeight ? scaleWidth : scaleHeight;
CGAffineTransform transform = CGAffineTransformMakeScale(scale, scale);
[UIView animateWithDuration:duration delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
[self.delegate makeupAnimationStateTransmitView:transView
data:transmitData
containerView:containerView];
fromView.alpha = 0;
fromView.center = finalCenter;
fromView.transform = transform;
} completion:^(BOOL finished) {
if (transitionContext.transitionWasCancelled) {
[transToVc completeTransitionWithView:transView data:transmitData];
} else {
[self.delegate completeTransitionWithView:transView data:transmitData];
}
[transitionContext completeTransition:!transitionContext.transitionWasCancelled];
}];
}
@end
@interface LBTransition () <UIGestureRecognizerDelegate, UIViewControllerTransitioningDelegate>
@property (nonatomic, weak) UIViewController <LBTransitionDelegate> *presentVC;
@property (nonatomic, weak) id <UIViewControllerContextTransitioning> transitionContext;
@property (nonatomic, assign) CGPoint startLocation;
@property (nonatomic, assign) CGFloat animationDuration;
@property (nonatomic, strong) UIView *transView;
@property (nonatomic, assign) CGFloat transViewCornerRadius;
@property (nonatomic, strong) id transmidData;
@property (nonatomic, strong) UIView *snapshotView;
@property (nonatomic, assign) CGRect origionFrame;
@property (nonatomic, assign) CGRect transViewFrame;
@end
@implementation LBTransition
- (instancetype)initWithDelegate:(id<LBTransitionDelegate>)delegate
{
if (self = [super init]) {
self.delegate = delegate;
self.origionFrame = CGRectZero;
self.transViewFrame = CGRectZero;
self.animationDuration = 0.25;
}
return self;
}
#pragma mark - UINaivgationControllerDelegate
- (id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController
animationControllerForOperation:(UINavigationControllerOperation)operation
fromViewController:(UIViewController *)fromVC
toViewController:(UIViewController *)toVC
{
if (operation == UINavigationControllerOperationPush) {
return [[LBPushAnimation alloc] initWithDelegate:self.delegate];
} else {
return [[LBPopAnimatin alloc] initWithDelegate:self.delegate];
}
}
- (id<UIViewControllerInteractiveTransitioning>)navigationController:(UINavigationController *)navigationController
interactionControllerForAnimationController:(id<UIViewControllerAnimatedTransitioning>)animationController
{
if (!self.interacting) {
return nil;
}
return self;
}
- (void)wireToViewController:(UIViewController<LBTransitionDelegate> *)viewController
{
self.presentVC = viewController;
UIPanGestureRecognizer *gesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handleGesture:)];
gesture.delegate = self;
[viewController.view addGestureRecognizer:gesture];
self.gesture = gesture;
}
- (void)startInteractiveTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
if (self.gesture.state == UIGestureRecognizerStatePossible) {
dispatch_async(dispatch_get_main_queue(), ^{
[transitionContext completeTransition:NO];
});
return;
}
self.transitionContext = transitionContext;
UIView *containerView = [transitionContext containerView];
UIView *toView = [transitionContext viewForKey:UITransitionContextToViewKey];
UIViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIView *fromView = fromVC.view;
[containerView insertSubview:toView belowSubview:fromView];
self.transmidData = [self.presentVC transmitViewData];
CGRect finalFrame = [self.delegate transmitViewfinalFrameWithData:self.transmidData containerView:containerView];
self.transView = [self.presentVC prepareTransimitView:containerView finalFrame:finalFrame];
self.transViewCornerRadius = self.transView.layer.cornerRadius;
self.snapshotView = [fromView snapshotViewAfterScreenUpdates:NO];
[self.snapshotView addSubview:self.transView];
self.origionFrame = self.snapshotView.frame;
self.transViewFrame = self.transView.frame;
[containerView addSubview:self.snapshotView];
fromView.alpha = 0;
}
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
return YES;
}
#pragma mark - action
- (void)handleGesture:(UIPanGestureRecognizer *)gestureRecognizer
{
CGFloat width = gestureRecognizer.view.superview.frame.size.width;
CGPoint location = [gestureRecognizer locationInView:gestureRecognizer.view.superview];
switch (gestureRecognizer.state) {
case UIGestureRecognizerStateBegan:
{
self.interacting = YES;
id transmitData = [self.presentVC transmitViewData];
[self.delegate prepareAnimationWithData:transmitData];
self.startLocation = location;
UINavigationController *navigationController = self.presentVC.navigationController;
id <UINavigationControllerDelegate> navigationControllerDelegate = navigationController.delegate;
navigationController.delegate = self;
[self.presentVC.navigationController popViewControllerAnimated:YES];
navigationController.delegate = navigationControllerDelegate;
}
break;
case UIGestureRecognizerStateChanged:
{
CGPoint trans = CGPointMake(location.x - self.startLocation.x, location.y - self.startLocation.y);
CGFloat minScale = 0.3;
CGFloat scale = (width - fabs(trans.x))/ width * (1 - minScale) + minScale;
CGPoint transViewCenter = CGPointMake(self.transViewFrame.origin.x + self.transViewFrame.size.width * 0.5, self.transViewFrame.origin.y + self.transViewFrame.origin.y + self.transViewFrame.size.height * 0.5);
self.snapshotView.frame = CGRectMake(0, 0, self.origionFrame.size.width * scale, self.origionFrame.size.height * scale);
self.snapshotView.center = CGPointMake(self.origionFrame.size.width * 0.5 + trans.x, self.origionFrame.size.height * 0.5 + trans.y);
transViewCenter.x *= scale;
transViewCenter.y *= scale;
self.transView.frame = CGRectMake(0, 0, self.transViewFrame.size.width * scale, self.transViewFrame.size.height * scale);
self.transView.layer.cornerRadius = self.transViewCornerRadius * scale;
self.transView.center = transViewCenter;
}
break;
case UIGestureRecognizerStateCancelled:
case UIGestureRecognizerStateEnded:
{
UIView *containerView = [self.transitionContext containerView];
UIView *toView = [self.transitionContext viewForKey:UITransitionContextToViewKey];
CGPoint trans = CGPointMake(location.x - self.startLocation.x, location.y - self.startLocation.y);
if (fabs(trans.x) < (width * 0.15)) {
[UIView animateWithDuration:self.animationDuration delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
self.snapshotView.frame = self.origionFrame;
self.transView.frame = self.transViewFrame;
self.transView.layer.cornerRadius = 12;
} completion:^(BOOL finished) {
[toView removeFromSuperview];
self.presentVC.view.alpha = 1;
[self.snapshotView removeFromSuperview];
[self.presentVC completeTransitionWithView:self.transView data:self.transmidData];
[self.delegate completeTransition];
self.interacting = NO;
[self cancelInteractiveTransition];
[self.transitionContext completeTransition:NO];
}];
} else {
CGRect cFrame = [self.snapshotView convertRect:self.transView.frame toView:containerView];
[containerView addSubview:self.transView];
self.transView.frame = cFrame;
NSTimeInterval duration = self.animationDuration;
[UIView animateKeyframesWithDuration:duration delay:0 options:UIViewKeyframeAnimationOptionCalculationModeLinear animations:^{
[UIView addKeyframeWithRelativeStartTime:0 relativeDuration:0.1 animations:^{
self.snapshotView.alpha = 0;
}];
[UIView addKeyframeWithRelativeStartTime:0.1 relativeDuration:1 animations:^{
[self.delegate makeupAnimationStateTransmitView:self.transView
data:self.transmidData
containerView:containerView];
}];
} completion:^(BOOL finished) {
[self.snapshotView removeFromSuperview];
[self.delegate completeTransitionWithView:self.transView data:self.transmidData];
self.interacting = NO;
[self finishInteractiveTransition];
[self.transitionContext completeTransition:YES];
}];
}
break;
}
default:
break;
}
}
@end
```![请添加图片描述](https://img-blog.csdnimg.cn/direct/c1e3f47c8be74f6d95a9062735b2d36e.gif)