美文网首页
IOS手势滑动返回总结(边缘+全屏)

IOS手势滑动返回总结(边缘+全屏)

作者: WTF丶程序猿 | 来源:发表于2021-05-21 10:44 被阅读0次

    为了提高用户体验,在controller会加上这个操作,我自己写了好多次,但是没有系统的整理过,这会儿又做到这个功能了,索性整理一下。

    一、边缘滑动返回

    在远古时代,大概是ios7之前,滑动返回这个事儿是不被官方支持的,因为手机屏幕没那么大,IOS7以后,苹果为了提升用户体验,增加了【边缘返回】的手势,注意是边缘,不是全屏,并且在特定条件下,边缘返回会失效,具体是以下几种情况:

    1. 自定义了navigationItem的leftBarButtonItem或leftBarButtonItems

    2. self.navigationItem.hidesBackButton = YES

    3. self.navigationItem.leftItemsSupplementBackButton = NO

    为了解决以上问题,有两个方案,拿捏:

    方案一:

    在UINavigationController基类添加以下:

    - (void)viewDidLoad

    {

        [super viewDidLoad];

        //设置右滑返回手势的代理为自身

        __weak typeof(self) weakself = self;

        if ([self respondsToSelector:@selector(interactivePopGestureRecognizer)]) {

            self.interactivePopGestureRecognizer.delegate = (id)weakself;

        }

    }

    #pragma mark - UIGestureRecognizerDelegate

    //这个方法是在手势将要激活前调用:返回YES允许右滑手势的激活,返回NO不允许右滑手势的激活

    - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer

    {

        if (gestureRecognizer == self.interactivePopGestureRecognizer) {

            //屏蔽调用rootViewController的滑动返回手势,避免右滑返回手势引起crash

            if (self.viewControllers.count < 2 ||

    self.visibleViewController == [self.viewControllers objectAtIndex:0]) {

                return NO;

            }

        }

        //这里就是非右滑手势调用的方法啦,统一允许激活

        return YES;

    }

    那么,在特定场景下,我们不希望用户轻易返回,比如在直播间内、在扫码界面等,拿捏:

    创建一个UIViewController 的分类:

    + (void)popGestureClose:(UIViewController *)VC

    {

        // 禁用侧滑返回手势

        if ([VC.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)]) {

            //这里对添加到右滑视图上的所有手势禁用

            for (UIGestureRecognizer *popGesture in VC.navigationController.interactivePopGestureRecognizer.view.gestureRecognizers) {

                popGesture.enabled = NO;

            }

            //若开启全屏右滑,不能再使用下面方法,请对数组进行处理

            //VC.navigationController.interactivePopGestureRecognizer.enabled = NO;

        }

    }

    + (void)popGestureOpen:(UIViewController *)VC

    {

        // 启用侧滑返回手势

        if ([VC.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)]) {

        //这里对添加到右滑视图上的所有手势启用

            for (UIGestureRecognizer *popGesture in VC.navigationController.interactivePopGestureRecognizer.view.gestureRecognizers) {

                popGesture.enabled = YES;

            }

            //若开启全屏右滑,不能再使用下面方法,请对数组进行处理

            //VC.navigationController.interactivePopGestureRecognizer.enabled = YES;

        }

    }

    使用:

    - (void)viewDidAppear:(BOOL)animated

    {

        [super viewDidAppear:animated];

        [UIViewController popGestureClose:self]; //关闭边缘返回

    }

    - (void)viewWillDisappear:(BOOL)animated

    {

        [super viewWillDisappear:animated];

        [UIViewController popGestureOpen:self]; //启动边缘返回

    }

    方案二:

    每个UIViewController都有一个backBarButtonItem,这是个特殊属性,只响应页面的返回和销毁,表现为:只能自定义image和title,不能重写target 或 action。(注意:UINavigationController的左侧是不支持右滑返回手势的)我们通过自定义backBarButtonItem,来实现:既实现“自定义返回按钮(通常自定义leftBarButtonItem或leftBarButtonItems都是为了实现自定义返回按钮)”又保留滑动返回。

    拿捏:

    在UIViewController基类:

    - (void)viewDidLoad{

        [super viewDidLoad];

    UIBarButtonItem *backItem = [[UIBarButtonItem alloc] initWithTitle:@"" style:UIBarButtonItemStylePlain target:nil action:nil];

         //自定义返回按钮的视图

         [self.navigationController.navigationBar setBackIndicatorImage:[UIImage imageNamed:@"navi_back_icon"]];

         [self.navigationController.navigationBar setBackIndicatorTransitionMaskImage:[UIImage imageNamed:@"navi_back_icon"]];

         //设置tintColor 改变自定图片颜色

         self.navigationController.navigationBar.tintColor = [UIColor whiteColor];

         //设置自定义的返回按钮

         self.navigationItem.backBarButtonItem = backItem;

    }

    那么在这种方案下,在特定场景我们不希望用户轻易返回,如何做?

    拿捏:

    自定义`leftBarButtonItem`或`leftBarButtonItems`,并设置`leftItemsSupplementBackButton = YES`。

    - (void)viewDidLoad{

        [super viewDidLoad];

         //自定义返回按钮

         UIButton *backBtn = [UIButton buttonWithType:UIButtonTypeCustom];

         [studySearch setImage:[UIImage imageNamed:@"back_icon"] forState:UIControlStateNormal];

         [studySearch sizeToFit];

         [studySearch addTarget:self action:@selector(backAction) forControlEvents:UIControlEventTouchUpInside];

         UIBarButtonItem *studySearchItem = [[UIBarButtonItem alloc] initWithCustomView:studySearch];

         self.navigationItem.leftBarButtonItems = @[studySearchItem];

         //是否支持显示左滑返回按钮,

         //NO不显示:leftBarButtonItems覆盖backBarButtonItem,

         //YES显示:backBarButtonItem 显示在leftBarButtonItems左侧

         //leftItemsSupplementBackButton必须在自定义leftBarButtonItem或leftBarButtonItems后才有效

         self.navigationItem.leftItemsSupplementBackButton = YES;

    }

    以上两个方案已经可以满足大部分开发需求,但还有一种情况,在UIScrollView(UICollectionView)下,返回手势会失灵。

    我们先来看看啥原理:

    UIScrollView(包括其子类UITextView、UITableView、UICollectionView等)的panGestureRecognizer先接收到手势事件,处理后不再往下传递。即是否让两个panGestureRecognizer都起作用的问题,默认情况下scrollView的手势会让系统的手势失效。so,显而易见,我们需要让两个手势同时启用。

    拿捏:

    创建UIScrollView的分类

    @implementation UIScrollView (PopGesture)

    //此方法返回YES时,手势事件会一直往下传递,不论当前层次是否对该事件进行响应。

    - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer{

        return YES;

    }

    - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer{

        return YES;

    }

    @end

    二、全屏滑动返回

    全屏返回这种骚功能,官方从未提供过,可爱的程序员们自己搞出来,以前的做法(ios7之前)大概就是“手势+截图”,画风是这样的:

    - (void)viewDidLoad{

        [super viewDidLoad];

        UIImageView *shadowImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"leftside_shadow_bg"]];

        shadowImageView.frame = CGRectMake(-10, 0, 10, self.view.frame.size.height);

        [self.view addSubview:shadowImageView];

        UIPanGestureRecognizer *recognizer = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(paningGestureReceive:)];

        [recognizer setDelegate:self];

        [recognizer delaysTouchesBegan];

        [self.view addGestureRecognizer:recognizer];

    }

    - (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated{

        if (self.viewControllers.count > 0) {

            [self.screenShotsList addObject:[self capture]]; //截图,并放入数组

        }

        [super pushViewController:viewController animated:animated];

    }

    - (nullable NSArray<__kindof UIViewController *> *)popToRootViewControllerAnimated:(BOOL)animated {

        [self.screenShotsList removeAllObjects]; //清空截图

        return [super popToRootViewControllerAnimated:animated];

    }

    -感兴趣的同学可以在这里看到完整代码 -

    自从ios7支持【边缘滑动】返回后,【全屏返回】的实现又多了一种思路,江湖人称:移花接木。同样有两个方案。

    拿捏:

    方案一:

    在UINavigationController基类中:

    - (void)viewDidLoad{

        [super viewDidLoad];

        // 获取系统自带滑动手势的target对象

        id target = self.interactivePopGestureRecognizer.delegate;

        // 创建全屏滑动手势,调用系统自带滑动手势的target的action方法

        SEL handler = NSSelectorFromString(@"handleNavigationTransition:");

        UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:target action:handler];

    //设置手势代理,拦截手势触发

    pan.delegate = self;

    //添加全屏滑动手势

    [self.interactivePopGestureRecognizer.view addGestureRecognizer:pan];

    // 禁止使用系统自带的边缘滑动手势

    self.interactivePopGestureRecognizer.enabled = NO;

        //设置右滑返回手势的代理为自身

        __weak typeof(self) weakself = self;

        if ([self respondsToSelector:@selector(interactivePopGestureRecognizer)]) {

            self.interactivePopGestureRecognizer.delegate = (id)weakself;

        }

    }

    注意,这里的  pan.delegate = self;  可能系统会打警告⚠️,因为没有申明和实现代理  UIGestureRecognizerDelegate  ,不实现也木有关系的,不过实现的话,可以再加一些判断,出于安全和为了去掉警告,我们来加一下。

    拿捏:

    @interface UIViewController()<UIGestureRecognizerDelegate>

    @end

    ... ...

    - (BOOL)gestureRecognizerShouldBegin:(UIPanGestureRecognizer *)gestureRecognizer{

        //控制器栈里只有一个,不响应

        if (self.navigationController.viewControllers.count <= 1) {

            return NO;

        }

        // 当控制器正在返回的时候,不响应

        if ([[self.navigationController valueForKey:@"_isTransitioning"] boolValue]) {

            return NO;

        }

        //只能响应 从左到右的滑动

        CGPoint translation = [gestureRecognizer translationInView:gestureRecognizer.view];

        if (translation.x <= 0) {

            return NO;

        }

        return YES;

    }

    还有一处细节,每个UIViewController都会默认添加 navigationController.interactivePopGestureRecognizer手势,而我们再基类又给加了一次,这不是变成两个interactivePopGestureRecognizer了吗,既然如此,我们禁用掉一个!

    拿捏:

    在UIViewController基类中:

    - (void)viewDidLoad{

        [super viewDidLoad];

      if(self.navigationController.interactivePopGestureRecognizer.view.gestureRecognizers.count == 2 ){

            for (UIGestureRecognizer *popGesture in self.navigationController.interactivePopGestureRecognizer.view.gestureRecognizers) {

                popGesture.enabled = NO;

                break;

            }

        }

    }

    方案二

    此方案最方便快捷。

    pod 'FDFullscreenPopGesture'

    pod 'TZScrollViewPopGesture'

    - FDFullscreenPopGesture 为每个UIViewController添加【全屏滑动返回】,但遇到UIScrollView就无效了

    - TZScrollViewPopGesture 主要是实现【边缘滑动返回】功能,不过这个是次要,主要是,它提供了给UIScrollView添加【边缘滑动返回】的功能。

    这俩不会互相影响,功能互补,详细原理可以去看看源码,这儿就不细写了。

    作者:Helios_CC

    链接:https://www.jianshu.com/p/0c698f71c49a

    来源:简书

    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

    相关文章

      网友评论

          本文标题:IOS手势滑动返回总结(边缘+全屏)

          本文链接:https://www.haomeiwen.com/subject/ynfwjltx.html