iOS开发--下滑返回dismiss

简介

现在越来越多的应用有看大图或者进入详情页面,但是,再返回的时候,普通意义上,会点击左上角的返回,这时候你就会发现,还需要将手指移动到左上角,这样,无意给用户增添了麻烦,并且,现在手机屏幕越来越大,这样返回的越来越困难,在体验上特别的差劲.

尽管苹果推出了从左边缘右滑返回,FDFullscreenPopGesture这个很强大的,全屏右滑返回…..尽管现在考虑到用户体验上,已经有了很大的提升,但是,仍然,在大屏上,不是很好操作…因为,你在正常使用手机的时候,大拇指使用的频率要远远大于其他手指,而且,大拇指,上下滑动的体验度是要大于左右滑动的体验度的…..所以,这里就有着下滑返回的需求必要的…

开始

解决完需求的原因,下面我们来看看如何做?

1.转场动画的设置

1. 遵循协议

需要转场的ViewCointroller遵循:UIViewControllerTransitioningDelegate

2. 转场代理设置
// 设置Presented的动画
- (id<UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source {
    return [[PresentVCAnimation alloc] init];
}

/// 设置Dismiss返回的动画设置
- (id<UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed {
    return [[DismissVCAnimation alloc] init];
}

/// 设置过场动画
- (id<UIViewControllerInteractiveTransitioning>)interactionControllerForDismissal:(id<UIViewControllerAnimatedTransitioning>)animator {
    return (self.interactiveTransition.isInteracting ? self.interactiveTransition : nil);
}

2. PresentVCAnimation

PresentVCAnimation.h
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@interface PresentVCAnimation : NSObject<UIViewControllerAnimatedTransitioning>

@end
PresentVCAnimation.m
#import "PresentVCAnimation.h"

@implementation PresentVCAnimation
// 设置动画的时间长度
-(NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext {
    return 0.25;
}

-(void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext {
    // 前一个ViewController,动画的发起者
    UIViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
    // 后一个ViewController,动画的结束者
    UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
    // 转场动画的最终的frame
    CGRect finalFrameForVC = [transitionContext finalFrameForViewController:toVC];
    // 下面敲黑板啦
    // 转换的容器view,这里是存放转场动画的容器
    UIView *containerView = [transitionContext containerView];
    // 这里一般情况下,没有涉及到VC的View放大或者缩小,即可看做是屏幕的尺寸
    CGRect bounds = [UIScreen mainScreen].bounds;
    // 这是后一个ViewController的frame
    toVC.view.frame = CGRectOffset(finalFrameForVC, 0, bounds.size.height);
    [containerView addSubview:toVC.view];

    // 下面是改变前一个ViewController和后一个ViewController的动画
    [UIView animateWithDuration:[self transitionDuration:transitionContext] delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{
        fromVC.view.alpha = 0.5;
        toVC.view.frame = finalFrameForVC;
    } completion:^(BOOL finished) {
        [transitionContext completeTransition:YES];
        fromVC.view.alpha = 1.0;
    }];
}

-(void)animationEnded:(BOOL)transitionCompleted {
}

3.DismissVCAnimation

DismissVCAnimation.h
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@interface DismissVCAnimation : NSObject<UIViewControllerAnimatedTransitioning>

@end
DismissVCAnimation.m
#import "DismissVCAnimation.h"

@implementation DismissVCAnimation
-(NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext {
    return 0.25;
}

-(void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext {
    // 前一个ViewController,动画的发起者
    UIViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
    // 后一个ViewController,动画的结束者
    UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];

    CGRect screenBounds = [UIScreen mainScreen].bounds;
    // 获取前一个页面的frame
    CGRect initFrame = [transitionContext initialFrameForViewController:fromVC];
     // 转场动画的toView的最终的frame
    CGRect finalFrame = CGRectOffset(initFrame, 0, screenBounds.size.height);
    // 转换的容器view
    UIView *containerView = [transitionContext containerView];
    // 下面这里是为了让转场动画衔接的更和谐,不然,下滑一点距离就直接看到之前页面的内容,体验不好
    UIView *bgView = [[UIView alloc] initWithFrame:fromVC.view.bounds];
    bgView.backgroundColor = [UIColor blackColor];
    [toVC.view addSubview:bgView ];

    [containerView addSubview:toVC.view];
    [containerView sendSubviewToBack:toVC.view];

    [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
        fromVC.view.frame = finalFrame;
        bgView.alpha = 0;
    } completion:^(BOOL finished) {
        [bgView removeFromSuperview];
        BOOL complate = [transitionContext transitionWasCancelled];
        [transitionContext completeTransition:(!complate)];
    }];
}

-(void)animationEnded:(BOOL)transitionCompleted {
}
@end

4.SwipeUpInteractiveTransition

这个是针对滑动手势对转场动画的影响的类.

这里面主要处理了滑动行为的状态,需要重点了解的是:
1.判断用户是不是存在返回意图的行为,这里面规定了两种行为,而这两种行为,均为在当前页面手势结束的时候处理

返回意图的行为的判断条件:
  • 当下滑距离大于当前屏幕比例的 0.382 (无耻的取了黄金比例的对半,因为,感觉有逼格,可进行相应调整)
  • 快速滑动行为,也就是滑动速度 : iOS开发–手势滑动的速度
SwipeUpInteractiveTransition.h
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>

@interface SwipeUpInteractiveTransition : UIPercentDrivenInteractiveTransition
/// 手势中...
@property (nonatomic, assign) BOOL isInteracting;
/// 完成动画
@property (nonatomic, assign) BOOL shouldComplete;
// 初始化
- (instancetype)initWithGestureViewController:(UIViewController *)gestureVC;
@end
SwipeUpInteractiveTransition.m
#define KEY_WINDOW  [[UIApplication sharedApplication].delegate window]

#import "SwipeUpInteractiveTransition.h"

@interface SwipeUpInteractiveTransition()
// 手势添加的View
@property (nonatomic, strong) UIViewController *gestureVC;
// 记录手势结束前的点击位置
@property (nonatomic, assign) CGPoint oldTranslation;
// 是不是需要返回,这里是需要猜想并判断用户是不是存在返回行为
@property (nonatomic, assign) BOOL isNeedDismiss;
@end

@implementation SwipeUpInteractiveTransition
- (instancetype)initWithGestureViewController:(UIViewController *)gestureVC
{
    self = [super init];
    if (self) {
        _gestureVC = gestureVC;
        // 添加手势
        UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(panGestureHandler:)];
        [_gestureVC.view addGestureRecognizer:pan];
    }
    return self;
}

- (void)panGestureHandler:(UIPanGestureRecognizer *)gesture {
    // 获取手势触控的点在作用View的相对位置
    CGPoint translation = [gesture translationInView:gesture.view];
    // 每次手势触发的时候,重置,也就是用户不存在返回意图
    self.isNeedDismiss = NO;
    switch (gesture.state) {
        case UIGestureRecognizerStateBegan: {
            // 手势开始
            // 交互动画判断
            _isInteracting = YES;
            [_gestureVC dismissViewControllerAnimated:YES completion:nil];

            break;
        }
        case UIGestureRecognizerStateChanged: {
        // 当前触控点的赋值
            self.oldTranslation = translation;
            // 触控点的转化,因为updateInteractiveTransition的参数范围是[0.1],所以这里需要左边比例的转换
            CGFloat fraction = (translation.y / KEY_WINDOW.bounds.size.height);
            // 保证范围
            fraction = fmin(fmaxf(fraction, 0.0), 1.0);
            // 这里进行滑动中的判断,取的是黄金比例,也就是,如果滑动距离占比约38%,即可判断用户存在返回的行为
            _shouldComplete = fraction > 0.382;
            // 更新进度
            [self updateInteractiveTransition:fraction];
            break;
        }
        case UIGestureRecognizerStateEnded: {
           // 这里是重要的判断
           // 如果用户存在快速向下滑动的行为(等同于全屏快速向右滑动返回的行为),self.isNeedDismiss为YES
           // 而这个判断的快速范围如下 
            CGPoint speed = [gesture velocityInView:gesture.view];
           // 这个数据经过了大量测试和舒适度的数据,大家可以参考,具体的还需要根据实际情况而定.可以看我的量
            if (speed.y >= 920) {
                self.isNeedDismiss = YES;
                _shouldComplete = YES;
            }
            // 手势交互结束
            _isInteracting = NO;
            // 下面进行之前判断的处理
            // 使用dispatch_source_t定时回调,进行改变self.oldTranslation的数据,进行模拟手势移动,因为如果直接调用[self cancelInteractiveTransition];或者[self updateInteractiveTransition:fraction];就会发现,动画瞬间变化,瞬间复位或者瞬间消失返回.考虑到用户体验,如下
            dispatch_queue_t quene = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
            //定时器模式  事件源
            dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, quene);
            //NSEC_PER_SEC是秒,*1是每秒
            dispatch_source_set_timer(timer, dispatch_walltime(NULL, 0), NSEC_PER_SEC * 0.0001, 0);
            //设置响应dispatch源事件的block,在dispatch源指定的队列上运行
            dispatch_source_set_event_handler(timer, ^{
                //回调主线程,在主线程中操作UI
                dispatch_async(dispatch_get_main_queue(), ^{
                    if (!_isNeedDismiss && (!_shouldComplete || gesture.state == UIGestureRecognizerStateCancelled)) {
                        if (self.oldTranslation.y <= 0) {
                        // 当满足条件,执行取消动画
                            [self cancelInteractiveTransition];
                            // 移除时间回调
                            dispatch_source_cancel(timer);
                        } else {
                        // 模拟上滑行为
                            CGFloat fraction = (self.oldTranslation.y / KEY_WINDOW.bounds.size.height);
                            fraction = fmin(fmaxf(fraction, 0.0), 1.0);
                            self.oldTranslation = CGPointMake(self.oldTranslation.x, self.oldTranslation.y - 0.3);

                            [self updateInteractiveTransition:fraction];
                        }
                    } else {
                        if (self.oldTranslation.y > KEY_WINDOW.bounds.size.height) {
                        // 当滑出当前屏幕,执行完成动画
                            [self finishInteractiveTransition];
                                                        // 移除时间回调
                            dispatch_source_cancel(timer);
                        } else {
                        // 模拟下滑行为
                            CGFloat fraction = (self.oldTranslation.y / KEY_WINDOW.bounds.size.height);
                            fraction = fmin(fmaxf(fraction, 0.0), 1.0);
                            self.oldTranslation = CGPointMake(self.oldTranslation.x, self.oldTranslation.y + 0.3);

                            [self updateInteractiveTransition:fraction];
                        }
                    }
                });
            });
            //启动源
            dispatch_resume(timer);
            break;
        }
        default:
            break;
    }
}
@end
  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值