封装了一个优雅的iOS全屏侧滑返回工具

news2025/4/1 15:20:17

思路
添加一个全屏返回手势,UIPangesturerecognizer,
1 手势开始
在手势开始响应的时候,将navigationController的delegate代理设置为工具类,在工具类中执行代理方法,- (nullable id )navigationController:(UINavigationController *)navigationController
和 - (nullable id )navigationController:(UINavigationController *)navigationController

都返回为侧滑工具对象,然后侧滑工具实现

  • (NSTimeInterval)transitionDuration:(nullable id )transitionContext {
    和 - (void)animateTransition:(id)transitionContext
    方法 , 并实现 - (void)startInteractiveTransition:(id )transitionContext {
    方法,

2 手势改变
计算滑动比例,我们以超过200就算1,根据translationInView 计算滑动比例

在手势的改变的状态下,
通过 UIView *toView = [self.transitionContext viewForKey:UITransitionContextToViewKey];
UIView *fromView = [self.transitionContext viewForKey:UITransitionContextFromViewKey];
UIView *containerView = self.transitionContext.containerView;
方法获取到要退回到的视图和来源视图,并修改frame,
达到两个视图移动的效果

3 手势结束,根据滑动距离和速度确定是完成返回还是取消返回

4 注意要添加弹簧动画,使交互效果更流畅优雅

效果图

请添加图片描述

代码如下

@interface LBTransitionManager : NSObject

@property (nonatomic, weak) UINavigationController *navigationController;

+ (instancetype)shareManager;

- (UIPanGestureRecognizer *)addPanGestureRecognizerToViewControllerIfNeeded:(UIViewController *)viewController ;

@end

//
//  LBTransitionManager.m
//  TEXT
//
//  Created by mac on 2025/3/30.
//

#import "LBTransitionManager.h"
#import <UIKit/UIKit.h>
#import "LBTransitionInteractivePopLinearAnimation.h"

@interface LBTransitionManager () <UINavigationControllerDelegate,
UIViewControllerAnimatedTransitioning,
UIViewControllerInteractiveTransitioning>

@property (nonatomic, strong) LBTransitionInteractivePopLinearAnimation *currentInteractiveAnimation;

@property (nonatomic, weak) UIPanGestureRecognizer *currentPanGestureRecognizer;

@property (nonatomic, strong) id <UIViewControllerContextTransitioning> transitionContext;

@end

@implementation LBTransitionManager

+ (instancetype)shareManager
{
    static dispatch_once_t onceToken;
    static LBTransitionManager *shareManager = nil;
    dispatch_once(&onceToken, ^{
        shareManager = [[self alloc] init];
    });
    return shareManager;;
}

- (UIPanGestureRecognizer *)addPanGestureRecognizerToViewControllerIfNeeded:(UIViewController *)viewController {
    UIPanGestureRecognizer *panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePan:)];
    [viewController.view addGestureRecognizer:panGestureRecognizer];
    return panGestureRecognizer;
}

#pragma mark - UINavigationControllerDelegate

- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
    
}

- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
    
}

- (nullable id <UIViewControllerInteractiveTransitioning>)navigationController:(UINavigationController *)navigationController
                                   interactionControllerForAnimationController:(id <UIViewControllerAnimatedTransitioning>)animationController {
    return self;
}

- (nullable id <UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController
                                            animationControllerForOperation:(UINavigationControllerOperation)operation
                                                         fromViewController:(UIViewController *)fromVC
                                                           toViewController:(UIViewController *)toVC {
        return self;
}

#pragma mark - UIViewControllerAnimationTransitioning

- (NSTimeInterval)transitionDuration:(nullable id <UIViewControllerContextTransitioning>)transitionContext {
    NSTimeInterval transitionDuration = 0.4;
    
    if ([self.currentInteractiveAnimation respondsToSelector:@selector(transitionDuration)]) {
        transitionDuration = [self.currentInteractiveAnimation transitionDuration];
    }
    
    return transitionDuration;
}

- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
    UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
      UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
}

#pragma mark - UIViewControllerInteractiveTransitioning

- (void)startInteractiveTransition:(id <UIViewControllerContextTransitioning>)transitionContext {
    NSLog(@"ttttttt startInteractiveTransition, current pan: %@", self.currentPanGestureRecognizer);
    
    [self updateViewsInteractionWithTransitionContext:transitionContext isEnabled:NO];

    id<LBTransitionAnimationProtocol> animation = [[LBTransitionInteractivePopLinearAnimation alloc] init];
    
    [animation prepareTransitionWithTransitionContext:transitionContext];

    if (self.currentPanGestureRecognizer == nil) {
        [animation finishTransitionIsCancel:YES completion:^(BOOL finished, BOOL isCancel, id<UIViewControllerContextTransitioning>  _Nonnull transitionContext) {
            [self finishInteractiveTransitionWithTransitionContext:transitionContext isCancel:isCancel isAnimationFinished:finished];
            
            [self updateViewsInteractionWithTransitionContext:transitionContext isEnabled:YES];
        }];
    } else {
        self.currentInteractiveAnimation = animation;
    }
}

#pragma mark - action

- (void)handlePan:(id)sender {
    if (![sender isKindOfClass:[UIPanGestureRecognizer class]]) return;
    
    UIPanGestureRecognizer *panGestureRecognizer = sender;
    CGPoint velocity = [panGestureRecognizer velocityInView:panGestureRecognizer.view.superview];
    CGPoint translation = [panGestureRecognizer translationInView:panGestureRecognizer.view.superview];
    CGFloat percentComplete = [self percentCompleteWithTranslation:translation];
    UIResponder *nextResponder = panGestureRecognizer.view.nextResponder;
    switch (panGestureRecognizer.state) {
        case UIGestureRecognizerStateBegan: {
            NSLog(@"ttttttt pan pop state began");
            
            CGFloat velocityX = velocity.x;
            CGFloat velocityY = velocity.y;
            
            if (velocityX == 0.0) {
                panGestureRecognizer.enabled = NO;
                panGestureRecognizer.enabled = YES;
                return;
            }
            self.currentPanGestureRecognizer = panGestureRecognizer;
            UINavigationController *navigationController = self.navigationController;
            id<UINavigationControllerDelegate> navigationControllerDelegate = navigationController.delegate;
            navigationController.delegate = self;
            NSLog(@"LIVDetailTransitionManager navigationControllerDelegate: %@", navigationController.delegate);
            [navigationController popViewControllerAnimated:YES];
            navigationController.delegate = navigationControllerDelegate;
        }
            break;
        case UIGestureRecognizerStateChanged: {
            NSLog(@"这里的animation %@", self.currentInteractiveAnimation);
            BOOL finishInstantly = [self.currentInteractiveAnimation updateTransitionWithPercentComplete:percentComplete translation:translation];
            
            if (finishInstantly) {
                self.currentPanGestureRecognizer.state = UIGestureRecognizerStateEnded;
            }
        }
            break;
        
        case UIGestureRecognizerStateCancelled:
              case UIGestureRecognizerStateFailed: {
                  NSLog(@"ttttttt pan pop state cancelled, failed, started: %@", self.currentInteractiveAnimation);

                  self.currentPanGestureRecognizer = nil;

                  [self.currentInteractiveAnimation finishTransitionIsCancel:YES completion:^(BOOL finished, BOOL isCancel, id<UIViewControllerContextTransitioning> transitionContext) {
                      [self finishInteractiveTransitionWithTransitionContext:transitionContext isCancel:isCancel isAnimationFinished:finished];
                  }];
              } break;
            
        case UIGestureRecognizerStateEnded: {
            BOOL isTransitionMadeProgress = percentComplete > 0.1;
            BOOL isPanRight = velocity.x > 0.0;
            BOOL shouldComplete = NO;
            shouldComplete = (isTransitionMadeProgress && isPanRight) || velocity.x > 100.0;

            NSLog(@"ttttttt pan pop state end: %d, %d, %d, %@", isTransitionMadeProgress, isPanRight, shouldComplete, self.currentInteractiveAnimation);
            self.currentPanGestureRecognizer = nil;
            __weak typeof(self) weakSelf = self;
            [self.currentInteractiveAnimation finishTransitionIsCancel:!shouldComplete completion:^(BOOL finished, BOOL isCancel, id<UIViewControllerContextTransitioning> transitionContext) {
                [self finishInteractiveTransitionWithTransitionContext:transitionContext isCancel:isCancel isAnimationFinished:finished];
                
                [self updateViewsInteractionWithTransitionContext:transitionContext isEnabled:YES];
            }];
        } break;
        default:
            break;
    }
}

- (CGFloat)percentCompleteWithTranslation:(CGPoint)translation {
    CGFloat translationX = translation.x;
    CGFloat translationXRangeMin = 0.0;
    CGFloat translationXRangeMax = 200.0;
    
    if (translationX < translationXRangeMin) {
        return 0.0;
    } else if (translationX > translationXRangeMax) {
        return 1.0;
    } else {
        return (translationX - translationXRangeMin) / (translationXRangeMax - translationXRangeMin);
    }
}

- (void)finishInteractiveTransitionWithTransitionContext:(id<UIViewControllerContextTransitioning>)transitionContext isCancel:(BOOL)isCancel isAnimationFinished:(BOOL)isAnimationFinished {
    NSLog(@"ttttttt finish interactive transition: %d, %d", isCancel, isAnimationFinished);
        
    id<LBTransitionAnimationProtocol> currentInteractiveAnimation = self.currentInteractiveAnimation;
    self.currentInteractiveAnimation = nil;

    if (isCancel) {
        [transitionContext cancelInteractiveTransition];
    } else {

        [transitionContext finishInteractiveTransition];
    }
    
    [transitionContext completeTransition:!isCancel];
}

- (void)updateViewsInteractionWithTransitionContext:(id<UIViewControllerContextTransitioning>)transitionContext isEnabled:(BOOL)isEnabled {
    UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
    UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];

    fromViewController.view.userInteractionEnabled = isEnabled;
    toViewController.view.userInteractionEnabled = isEnabled;
}

@end

//
//  LBTransitionInteractivePopAnimation.h
//  TEXT
//
//  Created by mac on 2025/3/30.

//

#import <Foundation/Foundation.h>
#import "LBTransitionAnimationProtocol.h"

NS_ASSUME_NONNULL_BEGIN

@interface LBTransitionInteractivePopLinearAnimation : NSObject <LBTransitionAnimationProtocol>

@end

NS_ASSUME_NONNULL_END

//
//  LBTransitionInteractivePopAnimation.m
//  TEXT
//
//  Created by mac on 2025/3/30.
//

#import "LBTransitionInteractivePopLinearAnimation.h"
#import <UIKit/UIKit.h>
#import "UIView+LBFrame.h"

@interface LBTransitionInteractivePopLinearAnimation ()

@property (strong, nonatomic) id<UIViewControllerContextTransitioning> transitionContext;

@property (strong, nonatomic) UIView *containerView;

@property (strong, nonatomic) UIView *animationContainerView;

@end

@implementation LBTransitionInteractivePopLinearAnimation

- (instancetype)init{
    self = [super init];
    if (self == nil) return nil;
    
    return self;
}

#pragma mark - Transition

- (NSTimeInterval)transitionDuration {
    return 0.4;
}

- (void)prepareTransitionWithTransitionContext:(id<UIViewControllerContextTransitioning>)transitionContext {
    self.transitionContext = transitionContext;

    UIView *containerView = transitionContext.containerView;
    containerView.backgroundColor = UIColor.clearColor;

    self.containerView = containerView;

    UIView *toView = [transitionContext viewForKey:UITransitionContextToViewKey];
    toView.frame = containerView.bounds;
    toView.x = -(containerView.width / 4.0);
    
    [self.containerView addSubview:toView];
    
    UIView *fromView = [transitionContext viewForKey:UITransitionContextFromViewKey];
    fromView.frame = containerView.bounds;
    
    [self.containerView addSubview:fromView];
}

- (BOOL)updateTransitionWithPercentComplete:(CGFloat)percentComplete translation:(CGPoint)translation {
    UIView *toView = [self.transitionContext viewForKey:UITransitionContextToViewKey];
    UIView *fromView = [self.transitionContext viewForKey:UITransitionContextFromViewKey];
    UIView *containerView = self.transitionContext.containerView;

    CGFloat toViewInitialX = containerView.x - (containerView.width / 4.0);
    CGFloat toViewMaxX = containerView.x;
    CGFloat fromViewInitialX = containerView.x;
    CGFloat fromViewMaxX = containerView.width;
    
    toView.x = containerView.x - (containerView.width / 4.0) + translation.x * 0.25;
    fromView.x = containerView.x + translation.x;
    
    if (toView.x < toViewInitialX) {
        toView.x = toViewInitialX;
    }
    
    if (toView.x > toViewMaxX) {
        toView.x = toViewMaxX;
    }
    
    if (fromView.x < fromViewInitialX) {
        fromView.x = fromViewInitialX;
    }
    
    if (fromView.x > fromViewMaxX) {
        fromView.x = fromViewMaxX;
    }
    
    [self.transitionContext updateInteractiveTransition:percentComplete];
    
    return NO;
}

- (void)finishTransitionIsCancel:(BOOL)isCancel completion:(LIVDetailTransitionAnimationCompletion)completion {
    UIView *toView = [self.transitionContext viewForKey:UITransitionContextToViewKey];
    UIView *fromView = [self.transitionContext viewForKey:UITransitionContextFromViewKey];
    UIView *containerView = self.transitionContext.containerView;

    CGFloat toViewInitialX = containerView.x - (containerView.width / 4.0);
    CGFloat toViewMaxX = containerView.x;
    CGFloat fromViewInitialX = containerView.x;
    CGFloat fromViewMaxX = containerView.width;

    [UIView animateWithDuration:[self transitionDuration] delay:0.0 usingSpringWithDamping:1.0 initialSpringVelocity:0.0 options:UIViewAnimationOptionLayoutSubviews | UIViewAnimationOptionCurveLinear animations:^{
        if (isCancel) {
            toView.x = toViewInitialX;
            fromView.x = fromViewInitialX;
        } else {
            toView.x = toViewMaxX;
            fromView.x = fromViewMaxX;
        }
    } completion:^(BOOL finished) {
        NSLog(@"interactive pop linear animation completion: %d, %d", finished, isCancel);

        if (completion != nil) {
            completion(finished, isCancel, self.transitionContext);
        }
    }];
}

@end

//
//  LBTransitionAnimationProtocol.h
//  TEXT
//
//  Created by mac on 2025/3/30.
//

#ifndef LBTransitionAnimationProtocol_h
#define LBTransitionAnimationProtocol_h
#import <UIKit/UIKit.h>

typedef void(^LIVDetailTransitionAnimationCompletion)(BOOL finished, BOOL isCancel, id<UIViewControllerContextTransitioning> transitionContext);

@protocol LBTransitionAnimationProtocol <NSObject>

- (void)prepareTransitionWithTransitionContext:(id<UIViewControllerContextTransitioning>)transitionContext;
- (BOOL)updateTransitionWithPercentComplete:(CGFloat)percentComplete translation:(CGPoint)translation; // 更新当前动画进度,返回 YES 代表需要马上结束转场
- (void)finishTransitionIsCancel:(BOOL)isCancel completion:(LIVDetailTransitionAnimationCompletion)completion;

- (NSTimeInterval)transitionDuration;

@end


#endif /* LBTransitionAnimationProtocol_h */

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2325266.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

HCIP-6 DHCP

HCIP-6 DHCP DHCP&#xff08;Dynamic Host Configuration Protocol&#xff0c;动态主机配置协议&#xff09; 手工配置网络参数存在的问题 灵活性差 容易出错 IP地址资源利用率低 工作量大 人员素质要求高 DHCP服务器按照如下次序为客户端选择IP地址: ①DHCP服务器的数…

opencv图像处理之指纹验证

一、简介 在当今数字化时代&#xff0c;生物识别技术作为一种安全、便捷的身份验证方式&#xff0c;正广泛应用于各个领域。指纹识别作为生物识别技术中的佼佼者&#xff0c;因其独特性和稳定性&#xff0c;成为了众多应用场景的首选。今天&#xff0c;我们就来深入探讨如何利…

记一道CTF题—PHP双MD5加密+”SALT“弱碰撞绕过

通过分析源代码并找到绕过限制的方法&#xff0c;从而获取到flag&#xff01; 部分源码&#xff1a; <?php $name_POST[username]; $passencode(_POST[password]); $admin_user "admin"; $admin_pw get_hash("0e260265122865008095838959784793");…

机器学习的一百个概念(3)上采样

前言 本文隶属于专栏《机器学习的一百个概念》&#xff0c;该专栏为笔者原创&#xff0c;引用请注明来源&#xff0c;不足和错误之处请在评论区帮忙指出&#xff0c;谢谢&#xff01; 本专栏目录结构和参考文献请见[《机器学习的一百个概念》 ima 知识库 知识库广场搜索&…

分秒计数器设计

一、在VsCode中写代码 目录 一、在VsCode中写代码 二、在Quartus中创建工程与仿真 1、建立工程项目文件md_counter 2、打开项目文件&#xff0c;创建三个目录 3、打开文件trl&#xff0c;创建md_counter.v文件 4、打开文件tb&#xff0c;创建md_counter_tb.v文件 5、用VsCod…

Flink介绍——发展历史

引入 我们整个大数据处理里面的计算模式主要可以分为以下四种&#xff1a; 批量计算&#xff08;batch computing&#xff09; MapReduce Hive Spark Flink pig流式计算&#xff08;stream computing&#xff09; Storm SparkStreaming/StructuredStreaming Flink Samza交互计…

12. STL的原理

目录 1. 容器、迭代器、算法 什么是迭代器? 迭代器的作用&#xff1f; 迭代器的类型&#xff1f; 迭代器失效 迭代器的实现细节&#xff1a; 2. 适配器 什么是适配器&#xff1f; 适配器种类&#xff1a; 3. 仿函数 什么是仿函数&#xff1f; 仿函数与算法和容器的…

OSPFv3 的 LSA 详解

一、复习&#xff1a; OSPFv3 运行于 IPv6 协议上&#xff0c;所以是基于链路&#xff0c;而不是基于网段&#xff0c;它实现了拓扑和网络的分离。另外&#xff0c;支持一个链路上多个进程&#xff1b;支持泛洪范围标记和泛洪不识别的报文&#xff08;ospfv2 的行为是丢弃&…

python 原型链污染学习

复现SU的时候遇到一道python原型链污染的题&#xff0c;借此机会学一下参考&#xff1a; 【原型链污染】Python与Jshttps://blog.abdulrah33m.com/prototype-pollution-in-python/pydash原型链污染 文章目录 基础知识对父类的污染命令执行对子类的污染pydash原型链污染打污染的…

入栈操作-出栈操作

入栈操作 其 入栈操作 汇编代码流程解析如下&#xff1a; 出栈操作 其 出栈操作 汇编代码流程解析如下&#xff1a;

C++ 多态:面向对象编程的核心概念(一)

文章目录 引言1. 多态的概念2. 多态的定义和实现2.1 实现多态的条件2.2 虚函数2.3 虚函数的重写/覆盖2.4 虚函数重写的一些其他问题2.5 override 和 final 关键字2.6 重载/重写/隐藏的对比 3. 纯虚函数和抽象类 引言 多态是面向对象编程的三大特性之一&#xff08;封装、继承、…

Python数据可视化-第3章-图表辅助元素的定制

教材 本书为《Python数据可视化》一书的配套内容&#xff0c;本章为第3章-图表辅助元素的定制 本章主要介绍了图表辅助元素的定制&#xff0c;包括认识常用的辅助元素、设置坐标轴的标签、设置刻度范围和刻度标签、添加标题和图例、显示网格、添加参考线和参考区域、添加注释文…

springboot实现异步导入Excel的注意点

springboot实现异步导入Excel 需求前言异步导入面临的问题实现异步如何导入大Excel文件避免OOM&#xff1f;异步操作后&#xff0c;如何通知导入结果&#xff1f;如何加快导入效率&#xff1f;将导入结果通知给用户后&#xff0c;如何避免重复通知&#xff1f; 优化点完结撒花&…

Linux练习——有关硬盘、联网、软件包的管理

1、将你的虚拟机的网卡模式设置为nat模式&#xff0c;给虚拟机网卡配置三个主机位分别为100、200、168的ip地址 #使用nmtui打开文本图形界面配置网络 [rootrhcsa0306 ~]# nmtui #使用命令激活名为 ens160 的 NetworkManager 网络连接 [rootrhcsa0306 ~]# nmcli c up ens160 #通…

论文阅读:GS-Blur: A 3D Scene-Based Dataset for Realistic Image Deblurring

今天介绍一篇 2024 NeurIPS 的文章&#xff0c;是关于真实世界去模糊任务的数据集构建的工作&#xff0c;论文作者来自韩国首尔大学 Abstract 要训练去模糊网络&#xff0c;拥有一个包含成对模糊图像和清晰图像的合适数据集至关重要。现有的数据集收集模糊图像的方式主要有两…

Cocos Creator Shader入门实战(七):RGB不同算法效果的实现,及渲染技术、宏定义、属性参数的延伸配置

引擎&#xff1a;3.8.5 您好&#xff0c;我是鹤九日&#xff01; 回顾 上篇文章&#xff0c;讲解了Cocos Shader如何通过setProperty动态设置材质的属性&#xff0c;以及设置属性时候的一些注意事项&#xff0c;比如&#xff1a; 一、CCEffect部分properties参数的设定后&…

算法学习记录:递归

递归算法的关键在于回复现场&#xff0c;dfs&#xff08;&#xff09;函数返回值、结束条件、它的作用。 目录 1.综合练习 2. 二叉树的深搜 1.综合练习 39. 组合总和 - 力扣&#xff08;LeetCode&#xff09; 关键在画出的决策树当中&#xff0c;前面使用过的2、3&#xff0c;…

可发1区的超级创新思路(python\matlab实现):MPTS+Lconv+注意力集成机制的Transformer时间序列模型

首先声明,该模型为原创!原创!原创!且该思路还未有成果发表,感兴趣的小伙伴可以借鉴! 应用场景 该模型主要用于时间序列数据预测问题,包含功率预测、电池寿命预测、电机故障检测等等。 一、模型整体架构(本文以光伏功率预测为例) 本模型由多尺度特征提取模块(MPTS)…

三、分类模块,通用组件顶部导航栏Navbar

1.封装通用组件顶部导航栏Navbar 不同效果 Component export struct MkNavbar {Prop title: string Prop leftIcon: ResourceStr $r("app.media.ic_public_left")ProprightIcon: ResourceStr $r("app.media.ic_public_more")PropshowLeftIcon: boolean…

PHY——LAN8720A 寄存器读写 (二)

文章目录 PHY——LAN8720A 寄存器读写 (二)工程配置引脚初始化代码以太网初始化代码PHY 接口实现LAN8720 接口实现PHY 接口测试 PHY——LAN8720A 寄存器读写 (二) 工程配置 这里以野火电子的 F429 开发板为例&#xff0c;配置以太网外设 这里有一点需要注意原理图 RMII_TXD0…