效果图.gif
前言
闲来无事翻着公司的项目总觉得用UIPageViewControlle封装的分页控制器既不能侧滑返回又有弹簧效果很不爽,于是开始折腾一下,期间遇到了一些坑,但终于柳暗花明。在此记录一下填坑过程,分享给大家。
目标
- 去掉UIPageViewController在Scroll样式下的弹簧效果。
- 实现分页控制器的侧滑返回。
开始
网上搜索了下禁止弹簧效果的相关这个问题,发现了一段代码:
__block UIScrollView *scrollView = nil;
[_pageViewController.view.subviews enumerateObjectsUsingBlock:^(__kindof UIView * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if ([obj isKindOfClass:[UIScrollView class]])
{
scrollView = (UIScrollView *)obj;
}
}];
if (scrollView ) scrollView.delegate = self;
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
scrollView.bounces = NO;
}
加上代码果然可以,但是好景不长,当我点击分段控制器分页后,滚动手势居然失效了这显然不是我想要的效果。并且直接改变UIScrollView的代理这种方法显然不好,虽然delegate初始值为空,但这种做法不安全,加上还要更改非公开view的属性,就更加的不安全了。然后我打印了UIPageViewController内的一些信息:
(lldb) po _pageViewController.view.subviews
<__NSArrayM 0x608000245550>(
<_UIQueuingScrollView: 0x7fcaad02d400; frame = (0 0; 375 667); clipsToBounds = YES; gestureRecognizers = <NSArray: 0x608000247050>; layer = <CALayer: 0x60800023fcc0>; contentOffset: {375, 0}; contentSize: {1125, 667}>
)
(lldb) po [_pageViewController.view.subviews[0] superclass]
UIScrollView
(lldb) po [_pageViewController.view.subviews[0] valueForKey:@"gestureRecognizers"]
<__NSArrayI 0x60000025b900>(
<UIScrollViewDelayedTouchesBeganGestureRecognizer: 0x6080001abb40; state = Possible; delaysTouchesBegan = YES; view = <_UIQueuingScrollView 0x7fcaad02d400>; target= <(action=delayed:, target=<_UIQueuingScrollView 0x7fcaad02d400>)>>,
<UIScrollViewPanGestureRecognizer: 0x7fcaac608b10; state = Possible; delaysTouchesEnded = NO; view = <_UIQueuingScrollView 0x7fcaad02d400>; target= <(action=handlePan:, target=<_UIQueuingScrollView 0x7fcaad02d400>)>; must-fail = {
<UIScrollViewPagingSwipeGestureRecognizer: 0x6080003c2490; state = Possible; view = <_UIQueuingScrollView 0x7fcaad02d400>; target= <(action=_handleSwipe:, target=<_UIQueuingScrollView 0x7fcaad02d400>)>>
}>,
<UIScrollViewPagingSwipeGestureRecognizer: 0x6080003c2490; state = Possible; view = <_UIQueuingScrollView 0x7fcaad02d400>; target= <(action=_handleSwipe:, target=<_UIQueuingScrollView 0x7fcaad02d400>)>; must-fail-for = {
<UIScrollViewPanGestureRecognizer: 0x7fcaac608b10; state = Possible; delaysTouchesEnded = NO; view = <_UIQueuingScrollView 0x7fcaad02d400>; target= <(action=handlePan:, target=<_UIQueuingScrollView 0x7fcaad02d400>)>>
}>
)
发现UIPageViewController的view下有一个叫UIQueuingScrollView的view,他的父类是UIScrollView,并且有三个手势其中UIScrollViewPanGestureRecognizer手势就是UIQueuingScrollView的父类UIScrollView公开的panGestureRecognizer手势,大家可以打印地址查看。既然可以直接拿到控制UIPageViewController翻页的手势那问题就清晰了,只需要在适当的时候禁止掉这个手势不就可以不让用户继续滚动了吗。
解决问题
1.关键方法
//UIGestureRecognizerDelegate 返回YES才响应手势 返回NO手势失效
- (BOOL)gestureRecognizerShouldBegin:(UIPanGestureRecognizer *)gestureRecognizer;
//UIGestureRecognizer 当otherGestureRecognizer手势失效时才相应调用此方法的手势
- (void)requireGestureRecognizerToFail:(UIGestureRecognizer *)otherGestureRecognizer;
2.手势
- 侧滑返回手势
- UIPageViewController的滚动手势
- 为UIQueuingScrollView新添加一个pan手势(fakePan)
3.关键代码
__block UIScrollView *scrollView = nil;
[_pageViewController.view.subviews enumerateObjectsUsingBlock:^(__kindof UIView * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if ([obj isKindOfClass:[UIScrollView class]]) scrollView = (UIScrollView *)obj;
}];
if(scrollView)
{
//新添加的手势,起手势锁的作用
_fakePan = [UIPanGestureRecognizer new];
_fakePan.delegate = self;
[scrollView addGestureRecognizer:_fakePan];
[scrollView.panGestureRecognizer requireGestureRecognizerToFail:self.navigationController.fd_fullscreenPopGestureRecognizer];
[scrollView.panGestureRecognizer requireGestureRecognizerToFail:_fakePan];
[_fakePan requireGestureRecognizerToFail:self.navigationController.fd_fullscreenPopGestureRecognizer];
}
//UIGestureRecognizerDelegate
- (BOOL)gestureRecognizerShouldBegin:(UIPanGestureRecognizer *)gestureRecognizer
{
CGPoint translation = [gestureRecognizer translationInView:gestureRecognizer.view];
if (translation.x <= 0)
{
return (_currentIndex == _vcArray.count - 1 && transitionFinish);
}
else
{
return (_currentIndex ==0 && transitionFinish);
}
}
4.说明
侧滑返回我是用的UINavigationController+FDFullscreenPopGesture分类,fd_fullscreenPopGestureRecognizer就是侧滑返回的手势。当_fakePan和fd_fullscreenPopGestureRecognizer手势不响时分页视图的滚动手势才响应。在最后一页左滑或第一页右滑时_fakePan才响应。fd_fullscreenPopGestureRecognizer的响应条件是UINavigationController+FDFullscreenPopGesture内部实现的我们这里只需要设置在显示第一页时才开启这个手势即可。
总结
- 通过手势之间的优先级(个人觉得“手势锁”更形象),实现了UIPageViewController的无弹簧效果和侧滑返回。
- 缺陷:快速滑动或一直拖动不放松时会出现弹簧效果,因为是手势实现的不可避免的会出现这样的问题,个人觉得不太影响效果。
- 如果不需要侧滑返回只要删除与侧滑手势有关的代码。
- 在实现公开API无法直接实现的效果是应该尽量的不去改动非公开属性,而是在此基础上增加实现。
网友评论
2018-01-05 18:44:02.281229+0800 PageViewController[7589:4390243] refreshPreferences: HangTracerDuration: 500
2018-01-05 18:44:02.281239+0800 PageViewController[7589:4390243] refreshPreferences: ActivationLoggingEnabled: 0 ActivationLoggingTaskedOffByDA:0
2018-01-05 18:46:12.306399+0800 PageViewController[7589:4390243] *** Assertion failure in -[UIPageViewController _flushViewController:animated:], /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKit/UIKit-3694.4.18/UIPageViewController.m:2124
(lldb)