上周有一个小伙子来到公司面试,笔试题目做的还算不错,但是面试的表现一塌糊涂,项目架构、业务逻辑、界面逻辑,基本上十问九不知。其中有一个很简单的问题—-关于
tableView
点击状态栏返回顶部,我后来问了一些朋友,发现还真是有很多人不清楚是怎么实现的,觉得可以拿出来说一说。
关于这个问题,大部分新手第一个想到的就是在状态栏的地方添加一个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];
运行后我们发现,居然成功了。瞬间感觉这东西不就这么简单吗?错
冷静下来,检查一下编译器我们发现,我们在手势绑定方法中,打印的信息并没有显示!!据此我们发现了两件事情:
- 我们实现的点击方法并没有执行。
- 在没有执行的前提下,点击状态栏
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
这样一个属性。也就是说,如果我们想实现一个可以覆盖statusBar
的view
,我们需要将这个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
的时候: scrollView
可以响应statusBar
的手势,则只需将其他scrollView的scrollsToTop
属性置为NO
就可以了。scrollView
响应statusBar
的手势,我们只能使用自定义的方式。scrollsToTop
属性上面提到,系统的UIScrollView
自带有点击顶部状态栏自动返回顶部的效果,其属性scrollsToTop
的默认值是YES
。
UIScrollView
时,默认点击statusBar
,该UIScrollView
具有返回顶部的效果。UIScrollView
时 UIScrollView
的scrollsToTop
属性值为YES
,则该UIScrollView
具有返回顶部的效果,其他UIScrollView
不具有该效果。UIScrollView
的scrollsToTop
属性值为YES
,则所有UIScrollView
都不具有该效果。实际开发中我们经常会有复杂的界面结构,其中会具有多个
UIScrollView
,我们要如何实现点击状态栏,让多个UIScrollView
返回顶部呢?其实前面的思路已经很接近了,自定义一个可以覆盖statusBar
的视图就可以了,当然为了更好的复用,本文中自定义了一个继承自NSObject
的TopWindow
。
为了方便在复杂的界面逻辑中,很好的使用,我们自定义一个
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;
}
TopWindowRootVC
的touchBegan
方法中完成操作,提供了一个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];
}
}
UIScrollView
自带有点击顶部状态栏自动返回顶部的效果UIScrollView
时,点击顶部状态栏自动返回顶部UIScrollView
时: UIScrollView
的scrollsToTop
属性值为YES
,则该UIScrollView
具有返回顶部的效果,其他UIScrollView
不具有该效果。UIScrollView
的scrollsToTop
属性值为YES
,则所有UIScrollView
都不具有该效果。UIScrollView
自动返回顶部,需要自定义一个可以覆盖statusBar
的UIWindow,而非UIView。