思路
添加一个全屏返回手势,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 */