iOS 点击状态栏返回顶部(多scrollView的实现)

来源:互联网 发布:灰原穷 知乎 编辑:程序博客网 时间:2024/05/21 06:15

前言

  上周有一个小伙子来到公司面试,笔试题目做的还算不错,但是面试的表现一塌糊涂,项目架构、业务逻辑、界面逻辑,基本上十问九不知。其中有一个很简单的问题—-关于tableView点击状态栏返回顶部,我后来问了一些朋友,发现还真是有很多人不清楚是怎么实现的,觉得可以拿出来说一说。

  • 前言
  • 错误的实现方式
    • 如何滚动到顶部
    • 错误的实现方法
      • 第一个问题 状态栏的层级级别
      • 第二个问题 点击状态栏回到了顶部
  • 正确的实现方式
    • 设置scrollsToTop属性
    • 自定义一个TopWindow
      • 实现代码
  • 总结

错误的实现方式

关于这个问题,大部分新手第一个想到的就是在状态栏的地方添加一个view,然后加一个点击效果/手势。
整体思路看上去并没有什么问题,OK,那我们就来按这个思路来实现一下。

如何滚动到顶部

没什么好说的,直接上代码,在任意地方实现以下代码都可以使_needBackToTopView回到顶部。

CGPoint offset = _needBackToTopView.contentOffset;offset.y = -_needBackToTopView.contentInset.top;[_needBackToTopView setContentOffset:offset animated:YES];

错误的实现方法

创建view绑定手势

_statusBarCoverView = ({        UIView *view = [[UIView alloc]initWithFrame:CGRectMake(0, 0, KBLScreenWidth , 20)];        view.backgroundColor = [UIColor redColor];        view;});[self.view addSubview:_statusBarCoverView];UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(statuseBarClick)];[_statusBarCoverView addGestureRecognizer:tap];

手势绑定的方法

NSLog(@"我被点击了");CGPoint offset = _tableView.contentOffset;offset.y = -_tableView.contentInset.top;[_tableView setContentOffset:offset animated:YES];

运行后我们发现,居然成功了。瞬间感觉这东西不就这么简单吗?
状态栏测试结果1

冷静下来,检查一下编译器我们发现,我们在手势绑定方法中,打印的信息并没有显示!!据此我们发现了两件事情:

  1. 我们实现的点击方法并没有执行。
  2. 在没有执行的前提下,点击状态栏tableView居然还是回到了顶部。

下面我们就一点点的分析一下这些疑问。

第一个问题: 状态栏的层级级别

首先,statusBar是一个特殊的view,始终位于程序的topLevel,我们创建的普通view的level比statusBar低很多。因此响应事件优先被statusBar截获。

查阅官方文档,关于层级关系有如下定义:

const UIWindowLevel UIWindowLevelNormal;  const UIWindowLevel UIWindowLevelAlert;  const UIWindowLevel UIWindowLevelStatusBar;  typedef CGFloat UIWindowLevel; 
  • 从高到低依次是UIWindowLevelAlert > UIWindowLevelStatusBar >UIWindowLevelNormal

看到windowLevel我们立马想到UIWindow具有windowLevel这样一个属性。也就是说,如果我们想实现一个可以覆盖statusBarview,我们需要将这个view继承自UIWindow,并且将层级Level设置的比UIWindowLevelStatusBar高才可以。

第二个问题: 点击状态栏回到了顶部

我们发现,在我们方法失效的情况下,点击状态栏,依然完成了tableView返回顶部的功能。

原来,系统的UIScrollView自带有点击顶部状态栏自动返回顶部的效果,但是这个效果是有限制的。

// When the user taps the status bar, the scroll view beneath the touch which is closest to the status bar will be scrolled to top, but only if its `scrollsToTop` property is YES, its delegate does not return NO from `shouldScrollViewScrollToTop`, and it is not already at the top.// On iPhone, we execute this gesture only if there's one on-screen scroll view with `scrollsToTop` == YES. If more than one is found, none will be scrolled.@property(nonatomic) BOOL  scrollsToTop __TVOS_PROHIBITED;          // default is YES.

这个手势只能作用在一个scrollView上,当发现多个时,手势将会失效。

  • 但实际开发当中,我们的视图结构里大都包含了很多个scrollView,当视图中具备多个scrollView的时候:
    1. 我们要想实现唯一一个scrollView可以响应statusBar的手势,则只需将其他scrollView的scrollsToTop属性置为NO就可以了。
    2. 我们要想实现多个scrollView响应statusBar的手势,我们只能使用自定义的方式。

正确的实现方式

设置scrollsToTop属性

上面提到,系统的UIScrollView自带有点击顶部状态栏自动返回顶部的效果,其属性scrollsToTop的默认值是YES

  • 当视图中只有一个UIScrollView时,默认点击statusBar,该UIScrollView具有返回顶部的效果。
  • 当视图中有多个UIScrollView
    1. 如果只有一个UIScrollViewscrollsToTop属性值为YES,则该UIScrollView具有返回顶部的效果,其他UIScrollView不具有该效果。
    2. 如果多个UIScrollViewscrollsToTop属性值为YES,则所有UIScrollView都不具有该效果。

自定义一个TopWindow

实际开发中我们经常会有复杂的界面结构,其中会具有多个UIScrollView,我们要如何实现点击状态栏,让多个UIScrollView返回顶部呢?其实前面的思路已经很接近了,自定义一个可以覆盖statusBar的视图就可以了,当然为了更好的复用,本文中自定义了一个继承自NSObjectTopWindow

实现代码

为了方便在复杂的界面逻辑中,很好的使用,我们自定义一个TopWindow继承自NSObject,由于在AppDelegate创建一个新的窗口必须给这个窗口设置一个根控制器,否则会报错,我们还要创建一个根控制器TopWindowRootVC。这样写的好处是可以在任意地方使用。

TopWindow的.h中提供两个方法。

+(void)show;+(void)hide;

.m的实现中,在initialize方法中对window进行初始化操作

windowTop = [[UIWindow alloc]initWithFrame:CGRectMake(0, 0, KBLScreenWidth, 20)];windowTop.backgroundColor = [UIColor clearColor];windowTop.windowLevel = UIWindowLevelStatusBar + 1.0f;windowTop.rootViewController = [TopWindowRootVC new];

实现.h中提供的两个方法:

+(void)show{    windowTop.hidden = NO;}+(void)hide{    windowTop.hidden = YES;}

TopWindowRootVCtouchBegan方法中完成操作,提供了一个searchView方法

UIWindow *window = [UIApplication sharedApplication].keyWindow;[self searchView:window];

searchView方法中我们需要找到我们需要滚动的scrollView

-(void)searchNeedScrollView:(UIView *)window{    [self dumpView:window atIndent:0];    for (UIScrollView *scrollV in _arr) {        if (/* 需要滚动的视图 */) {            CGPoint offset = scrollV.contentOffset;            offset.y = -scrollV.contentInset.top;            [scrollV setContentOffset:offset animated:YES];        }    }}

递归遍历window的所有子视图

- (void)dumpView:(UIView *)aView atIndent:(int)indent{    for (UIView *view in [aView subviews]){        if ([view isKindOfClass:[UIScrollView class]]) {            [_arr addObject:view];        }        [self dumpView:view atIndent:indent + 1];    }}

运行结果:以两个tableView为例
状态栏测试结果2

总结

  • 系统的UIScrollView自带有点击顶部状态栏自动返回顶部的效果
  • 当视图中只有一个UIScrollView时,点击顶部状态栏自动返回顶部
  • 当视图中有多个UIScrollView时:
    1. 如果只有一个UIScrollViewscrollsToTop属性值为YES,则该UIScrollView具有返回顶部的效果,其他UIScrollView不具有该效果。
    2. 如果多个UIScrollViewscrollsToTop属性值为YES,则所有UIScrollView都不具有该效果。
  • 如果想同时让多个UIScrollView自动返回顶部,需要自定义一个可以覆盖statusBar的UIWindow,而非UIView。
2 0