在我们IOS开发中,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.
从上面分析我们可以得出结论:我们必须保证窗口上scrollsToTop == YES的ScrollView(及其子类)同一时间内有且只有一个。这一样才能保证点击statusBar,该唯一存在的ScrollView能自动回到顶部。即这个手势只能作用在一个scrollView上,当发现多个时,手势将会失效。
在实际应用中,我们可能会有多个scrollView(包含UITableView/UICollectionView),如汽车之家、网易新闻、爱奇艺等等应用,这时候,系统默认的点击状态栏返回到顶部效果就会失效,
如何保证苹果自带的该功能一直好使呢?
解决方案一:
当前显示哪个tableView,哪个的scrollsToTop就设置为YES,其余的设置为NO;
解决方案二:自己实现
初级思路:在statusBar的区域添加一个遮盖,监听遮盖的点击事件 (用监听也可以就是有点low 下面有更好的方法用递归)
1.首先我们想到用UIView来做这个遮盖。但是,在这里我们使用UIView是着不住statusBar的,UIView会一直在statusBar的下面,所以不能接收点击事件。因为statusBar其实是一个UIWindow,且优先级高于下面的keyWindow。所以,添加的UIView会在statusBar的下面。
2.由于优先级的关系,我们可以用一个UIWindow来做遮盖,设置遮盖window的优先级高于statusBar即可。当然,设置最高优先级(UIWindowLevelAlert)肯定是可以的。然后给遮盖Window添加一个点击事件,背景色设置透明即可
代码如下
#import "AppDelegate.h"
@interface AppDelegate ()
@property(strong, nonatomic) UIWindow *coverStatusBarWindow; // 覆盖在statusBar上的透明窗口
@end
在代理方法中添加 coverStatusBarWindow
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
//添加coverStatusBarWindow 并让其显示出来
UIWindow * coverStatusBarWindow =[[UIWindow alloc]initWithFrame:CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, 20)];
coverStatusBarWindow.rootViewController = [[UIViewController alloc]init];
coverStatusBarWindow.backgroundColor = [UIColor clearColor];
coverStatusBarWindow.windowLevel = UIWindowLevelStatusBar+1;
[coverStatusBarWindow makeKeyAndVisible];
self.coverStatusBarWindow = coverStatusBarWindow;
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(coverWindowOnClicked)];
[self.coverStatusBarWindow addGestureRecognizer:tap];
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
self.window.backgroundColor = [UIColor grayColor];
// 创建一个控制器
UIViewController *vc = [[UIViewController alloc] init];
self.window.rootViewController = vc;
self.window.windowLevel = UIWindowLevelNormal;
// 让UIwindow成为keyWindow(主窗口),并且可见
[self.window1 makeKeyAndVisible];
// 给UIwindow添加一个输入框
UITextField *tf = [[UITextField alloc] init];
tf.frame = CGRectMake(10, 64, 100, 20);
tf.borderStyle = UITextBorderStyleRoundedRect;
[self.window addSubview:tf];
return YES;
}
//发通知可以让其他的页面监听到状态栏的点击
- (void)coverWindowOnClicked{
[[NSNotificationCenter defaultCenter]postNotificationName:@"onClickedStatusBarNotification" object:self userInfo:nil];
}
在其他控制器里面监听
- (void)viewDidLoad {
[super viewDidLoad];
[[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(onClickedStatusBar:) name:@"kOnClickedStatusBarNotification" object:nil];
}
- (void)onClickedStatusBar:(NSNotification *)noti{
//让当前所显示的tableView 回到最顶部 (偏移量看情况设定哦)
self.currentTableView.contentOffset = CGPointMake(0, 0);
}
- (void)dealloc{
[[NSNotificationCenter defaultCenter]removeObserver:self];
}
想移除coverStatusBarWindow 将其赋值为空
self.coverStatusBarWindow = nil;
这里面可以将 coverStatusBarWindow 抽出来封装一下 提供 创建sharedCoverStatusBarWindow的方法 和show 和 dismiss 的方法
自己可以去试一下
最终思路:
1).创建一个UIWindow,背景颜色设置成透明色,frame设置成statusBar的frame,覆盖掉statusBar
2).给这个window添加一个点击的手势,点击这个window就遍历keyWindow中所有的子控件,取出当前显示在眼前的UIScrollView,将其滑动到顶部
代码如下(封装了一下)
.h文件
//
// JHStatusBarScrollsToTopManager.h
// TestQuestion
//
// Created by Mark on 2016/10/28.
// Copyright © 2016年 Mark. All rights reserved.
// 点击头部的状态栏,当前显示在眼前的scrollView就会移动到最初的位置,用于解决有嵌套多个scrollView 系统scrollsToTop 禁用问题
#import <Foundation/Foundation.h>
@interface JHStatusBarScrollsToTopManager : NSObject
/**
生效
*/
+ (void)becomeEffective;
/**
失效
*/
+ (void)disabled;
@end
.m文件
//
// JHStatusBarScrollsToTopManager.m
// TestQuestion
//
// Created by Mark on 2016/10/28.
// Copyright © 2016年 淡蓝. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "JHStatusBarScrollsToTopManager.h"
@implementation JHStatusBarScrollsToTopManager
static UIWindow *statusBarWindow;
+ (void)becomeEffective {
if (statusBarWindow == nil) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
statusBarWindow = [[UIWindow alloc] init];
statusBarWindow.frame = [UIApplication sharedApplication].statusBarFrame;
statusBarWindow.backgroundColor = [UIColor clearColor];
statusBarWindow.windowLevel = UIWindowLevelStatusBar+1;
[statusBarWindow addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(topWindowClick)]];
statusBarWindow.hidden = NO;
});
}
else {
statusBarWindow.hidden = NO;
}
}
+ (void)disabled {
statusBarWindow.hidden = YES;//不用在赋值为空了 为了下次不用再次创建
}
- (void)topWindowClick {
// 用递归的思想找到所有的UIScrollView
UIWindow *keyWindow = [UIApplication sharedApplication].keyWindow;
[self searchAllScrollViewsInView:keyWindow];
}
/**
* 找到view里面的所有UIScrollView 并 取出当前显示在眼前的UIScrollView将其滑动到顶部
*/
- (void)searchAllScrollViewsInView:(UIView *)view {
// 这个for循环可以保证所有子控件都能传进来
for (UIView *subview in view.subviews) {
[self searchAllScrollViewsInView:subview];
}
// 如果不是UIScrollView,直接返回
if (![view isKindOfClass:[UIScrollView class]]) return;
//如果是scrollView则进行如下的处理
UIScrollView *scrollView = (UIScrollView *)view;
CGRect scrollViewRect = [scrollView convertRect:scrollView.bounds toView:nil];
CGRect keyWindowRect = [UIApplication sharedApplication].keyWindow.bounds;
// 如果scrollView的矩形框 跟 keywindow 没有重叠,(表示不是显示在眼前的UIScrollView)直接返回
if (!CGRectIntersectsRect(scrollViewRect, keyWindowRect)) return;
// 若scrollView与keyWindow重叠(是显示在眼前的UIScrollView)让UIScrollView滚动到最前面
[scrollView scrollRectToVisible:CGRectMake(0, 0, 1, 1) animated:YES];
}
@end