封装了一个优雅的iOS转场动画

效果图

代码

//
//  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)
相关推荐
依旧风轻2 小时前
精确计算应用的冷启动耗时
ios·swift·cold start
#摩斯先生16 小时前
iOS 真机打包,证书报错No signing certificate “iOS Distribution” found
ios
花生君16 小时前
如何使用Xcode查看iOS APP客户端日志
ios·cocoa·xcode
zhaocarbon17 小时前
SSZipArchive 解压后 中文文件名乱码问题
ios
KillerNoBlood18 小时前
Objective-C语法基础
ios·objective-c
依旧风轻21 小时前
iOS项目怎样进行二进制重排
ios·binary·reordering
分享者花花1 天前
数据恢复篇:5 款最佳 Mac 数据恢复软件
windows·macos·ios·智能手机·电脑·笔记本电脑·iphone
hzgisme2 天前
iOS 视图实现渐变色背景
ios·cocoa·swift
1024小神2 天前
swiftui中常用组件picker的使用,以及它的可选样式
前端·ios