前言 设置scrollsToTop
属性
系统默认是有 点击status bar ,scrollView自动滚动到顶部。如果一个页面有多个scrollView,就需要把不用滚动到顶部的scrollView.scrollToTop = No;
// 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 `-scrollViewShouldScrollToTop:`, and it is not already at the top.
当用户轻击状态栏时,触摸下方最接近状态栏的滚动视图将滚动到顶部,但只有它的“scrollsToTop”属性为YES时,它的delegate从“-scrollViewShouldScrollToTop:”不返回NO,并且它尚未位于顶部。
// 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.
在iPhone上,我们只有在屏幕上有一个“scrollsToTop”==YES.如果发现不止一个,则不会滚动。
@property(nonatomic) BOOL scrollsToTop __TVOS_PROHIBITED; // default is YES.
- 但实际开发当中,我们的视图结构里大都包含了很多个scrollView,当视图中具备多个scrollView的时候:
- 我们要想实现唯一一个scrollView可以响应statusBar的手势,则只需将其他scrollView的scrollsToTop属性置为NO就可以了。
- 我们要想实现多个scrollView响应statusBar的手势,我们只能使用自定义的方式。
状态栏的层级级别
首先,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高才可以。
总结
系统的UIScrollView
自带有点击顶部状态栏自动返回顶部的效果,其属性scrollsToTo
p的默认值是YES
- 当视图中只有一个
UIScrollView
时,点击顶部状态栏自动返回顶部 - 当视图中有多个
UIScrollView
时:
如果只有一个UIScrollView
的scrollsToTop
属性值为YES
,则该UIScrollView
具有返回顶部的效果,其他UIScrollView
不具有该效果。
如果多个UIScrollView
的scrollsToTop
属性值为YES
,则所有UIScrollView
都不具有该效果。
如果想同时让多个UIScrollView
自动返回顶部,需要自定义一个可以覆盖statusBar
的UIWindow
,而非UIView
。
0.需求 自定义一个TopWindow
实际开发中我们经常会有复杂的界面结构,其中会具有多个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];
}
}
1.需求 通过坐标转换来获取电池栏的点击事件
在项目开发的时候遇到的问题,点击了电池栏让tableView滑动到顶部,UIScrollView有一个scrollsToTop属性,可以让滚动视图滑动到顶部,但是当视图中有很多scrollView包括第三方等中的视图,设置这个属性就会变得非常的麻烦,而且不一定能解决问题;
那么我们可以通过坐标转换来获取电池栏的点击事件;要先禁掉scrollsToTop属性。
类似于微信聊天界面中,点击statusbar,会回到tableview会回到顶点, 然后再次点击 会获取先前保存再本地的若干条数据。
思路:
1.获取用户在UIWindow上所点击的坐标
2.判断该坐标是否在电池栏的矩形区域中
3.如果点击了电池栏的区域则发送一个通知
4.在需要的控制器里边添加观察者
5.执行点击事件
6.卸载监听
实现:
在AppDelegate.h中定义一个全局量:
static NSString * const kStatusBarTappedNotification = @"statusBarTappedNotification";
在AppDelegate.m中判断用户点击左边以及是否点击在电池栏
#pragma mark - Status bar touch tracking
- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesBegan:touches withEvent:event];
CGPoint location = [[[event allTouches] anyObject] locationInView:[self window]];
CGRect statusBarFrame = [UIApplication sharedApplication].statusBarFrame;
// 如果点击了电池栏则发送一个通知
if (CGRectContainsPoint(statusBarFrame, location)) {
[[NSNotificationCenter defaultCenter] postNotificationName:kStatusBarTappedNotification object:nil];
}
}
在需要电池栏点击事件的控制器中添加监听
Observe notification in the needed controller (e.g. in viewWillAppear):
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(statusBarTappedAction:) name:kStatusBarTappedNotification object:nil];
实现监听的方法
- (void)statusBarTappedAction:(NSNotification*)notification {
// 在这里写要处理电池栏的点击事件
}
最后卸载监听
Remove observer properly (e.g. in viewDidDisappear):
[[NSNotificationCenter defaultCenter] removeObserver:self name:kStatusBarTappedNotification object:nil];
2.一个横屏的scrollView里面放置了多个tableView
最近做项目,由于一个横屏的scrollView里面放置了多个tableView,但点击状态栏仍要求tableView滚动到顶部,
思路是这样的:
1 追踪UIStatusBar的touch事件,然后响应该事件。不要直接向statusBar添加事件,因为并没有什么卵用!我也不知道为什么没有什么卵有!
2 找到UIApplication的keyWindow,遍历keyWindow的所有子控件。如果是scrollView,而且显示在当前keyWindow,那么将其contentOffset的y值设置为原始值(0)。这里会用到递归去进行遍历。
实现:
1.首先我们追踪UIStatusBar的触摸事件,需要在AppDelegate里面加入以下代码
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesBegan:touches withEvent:event];
CGPoint location = [[[event allTouches] anyObject] locationInView:self.window];
CGRect statusBarFrame = [UIApplication sharedApplication].statusBarFrame;
if (CGRectContainsPoint(statusBarFrame, location)) {
[self statusBarTouchedAction];
}
}
2.然后在statusBarTouchedAction方法中将显示在当前keyWindow里面的scrollView滚动到顶部
- (void)statusBarTouchedAction {
[JMSUIScrollViewTool scrollViewScrollToTop];
}
3.下面来看JMSUIScrollViewTool
#import <Foundation/Foundation.h>
@interface JMSUIScrollViewTool : NSObject
+ (void)scrollViewScrollToTop;
@end
#import "JMSUIScrollViewTool.h"
@implementation JMSUIScrollViewTool
+ (void)scrollViewScrollToTop {
UIWindow *window = [UIApplication sharedApplication].keyWindow;
[self searchScrollViewInView:window];
}
+ (void)statusBarWindowClick {
UIWindow *window = [UIApplication sharedApplication].keyWindow;
[self searchScrollViewInView:window];
}
+ (BOOL)isShowingOnKeyWindow:(UIView *)view {
UIWindow *keyWindow = [UIApplication sharedApplication].keyWindow;
CGRect newFrame = [keyWindow convertRect:view.frame fromView:view.superview];
CGRect winBounds = keyWindow.bounds;
BOOL intersects = CGRectIntersectsRect(newFrame, winBounds);
return !view.isHidden && view.alpha > 0.01 && view.window == keyWindow && intersects;
}
+ (void)searchScrollViewInView:(UIView *)supView {
for (UIScrollView *subView in supView.subviews) {
if ([subView isKindOfClass:[UIScrollView class]] && [self isShowingOnKeyWindow:supView]) {
CGPoint offset = subView.contentOffset;
offset.y = -subView.contentInset.top;
[subView setContentOffset:offset animated:YES];
}
[self searchScrollViewInView:subView];
}
}
@end
网友评论