点击状态栏回到顶部的功能失效的解决办法
在我们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