在我們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