點(diǎn)擊狀態(tài)欄,滾動(dòng)視圖返回頂部的實(shí)現(xiàn)

一、前言

在iOS開發(fā)中,我們常常看到好多應(yīng)用都有這樣一個(gè)效果,如果一個(gè)應(yīng)用當(dāng)前頁是是一個(gè)滾動(dòng)視圖,當(dāng)用戶點(diǎn)擊狀態(tài)欄的時(shí)候,滾動(dòng)視圖會(huì)自動(dòng)的返回內(nèi)容的最頂部。
其實(shí)這個(gè)功能是UIScrollView自帶的,不需要我們手動(dòng)去實(shí)現(xiàn),只需要設(shè)置self.scrollView.scrollsToTop = YES即可(scrollsToTop默認(rèn)為YES)。但是這個(gè)屬性有一個(gè)前提是窗口下必須只有一個(gè)可滾動(dòng)的View才有效果。

// 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.

在實(shí)際應(yīng)用中,我們可能會(huì)有多個(gè)scrollView(包含UITableView/UICollectionView),如網(wǎng)易新聞、簡書、愛奇藝,騰訊視頻等等應(yīng)用,這時(shí)候,系統(tǒng)默認(rèn)的點(diǎn)擊狀態(tài)欄返回到頂部效果就會(huì)失效,原因是當(dāng)前window下多個(gè)scrollViewscrollsToTop屬性的值都是YES,系統(tǒng)不知道該讓哪一個(gè)scrollView滾動(dòng)到頂部,所以就什么也不干了。如果還要讓當(dāng)前視圖滾回頂部,只需要將除了當(dāng)前顯示的其它scrollViewscrollsToTop屬性設(shè)置成NO就行。
但是今天要講的是應(yīng)簡友要求,講一下自定義控件來實(shí)現(xiàn)此功能。就是《LYTopWindow》實(shí)現(xiàn)原理。

二、基本思路

1.獲取當(dāng)狀態(tài)欄的點(diǎn)擊事件
2.取得當(dāng)前window上顯示的scrollView
3.控制scrollView的偏移量,讓其滾動(dòng)到頂部

三、實(shí)現(xiàn)步驟

新建一個(gè)空白項(xiàng)目,我們可以查看應(yīng)用結(jié)構(gòu)可以知道,狀態(tài)欄是獨(dú)立出來的,并不在window上


空白應(yīng)用

所以我們不能拿到狀態(tài)欄直接操作,那么就需要自定義一個(gè)控件蓋在狀態(tài)欄上面,用來攔截狀態(tài)欄的點(diǎn)擊事件。

一、添加窗口

由于添加普通的控件都不能蓋在狀態(tài)欄上面,所有要新建一個(gè)普通的windowkeyWindow只能有一個(gè))蓋到狀態(tài)欄的上面。

  • AppDelegate創(chuàng)建一個(gè)新的窗口,從iOS9.0開始,程序啟動(dòng)結(jié)束時(shí)的窗口都必須設(shè)置rootViewController。否則會(huì)報(bào)以下錯(cuò)誤:
Application windows are expected to have a root view controller at the end of application launch

這里可以通過dispatch_after來給添加窗口一個(gè)延時(shí)就可以不設(shè)置根控制器而不會(huì)報(bào)錯(cuò)

  • UIWindow的顯示不需要添加到其它控件上,只需要將hidden設(shè)置成NO就行。
  • 要想讓自定義窗口蓋在狀態(tài)欄的上面還要設(shè)置窗口的級別windowLevel,窗口的級別有三種,分別是UIWindowLevelAlert > UIWindowLevelStatusBar > UIWindowLevelNormal,級別越高就越顯示在頂部,如果級別一樣,那么后創(chuàng)建添加的顯示在頂部。
static UIWindow *topWindow_;
-(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        topWindow_ = [[UIWindow alloc] init];
        topWindow_.frame = [UIApplication sharedApplication].statusBarFrame;
        topWindow_.backgroundColor = [UIColor clearColor];
        topWindow_.hidden = NO;
        topWindow_.windowLevel = UIWindowLevelAlert;
    });
    return YES;
}

然后在給topWindow添加一個(gè)手勢監(jiān)聽點(diǎn)擊事件就可以攔截到狀態(tài)欄的點(diǎn)擊事件了。
但是,這樣寫會(huì)有一個(gè)Bug,如果旋轉(zhuǎn)屏幕的話,window上的添加的Viewframe不會(huì)跟著屏幕旋轉(zhuǎn)而改變,就會(huì)不正確。

  • 如果需要屏幕旋轉(zhuǎn)的話,必須要給UIWindwo設(shè)置rootViewController;
  • Bug產(chǎn)生的原因是Autoresizing的影響,在旋轉(zhuǎn)的屏幕時(shí),窗口的View寬高被拉伸而造成frame不正確,這時(shí)只需要設(shè)置一下View跟隨窗口的變化而變化即可。
topWindow_.rootViewController.view.autoresizingMask = UIViewAutoresizingFlexibleWidth;

居然這里已經(jīng)設(shè)置了rootViewController,那么就不需要dispatch_after。

static UIWindow *topWindow_;
-(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    topWindow_ = [[UIWindow alloc] init];
    topWindow_.frame = [UIApplication sharedApplication].statusBarFrame;
    topWindow_.backgroundColor = [UIColor clearColor];
    topWindow_.hidden = NO;
    topWindow_.windowLevel = UIWindowLevelAlert;
    [topWindow_ addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(topWindowClick)]];
    return YES;
}

二、監(jiān)聽頂部窗口點(diǎn)擊,實(shí)現(xiàn)當(dāng)前滾動(dòng)視圖滾回頂部

/** * 監(jiān)聽頂部窗口點(diǎn)擊 */
+(void)topWindowClick{
    UIWindow *keyWindow = [UIApplication sharedApplication].keyWindow;
    [self searchAllScrollViewsInView:keyWindow];
}

查找當(dāng)前keyWindow上的滾動(dòng)視圖,如果跟keyWindow重疊,就讓其滾動(dòng)到頂部

/**
 *  查找view中的所有scrollView
 */
-(void)searchAllScrollViewsInView:(UIView *)view {
    // 如果不在UIWindow矩形框里面,就直接返回
    // view和UIWindow沒有重疊,就直接返回
    if (![view intersectWithView:nil]) return;
    
    for (UIView *subview in view.subviews) {
        [self searchAllScrollViewsInView:subview];
    }
    
    // 如果不是UIScrollView, 直接返回
    if (![view isKindOfClass:[UIScrollView class]]) return;
    
    UIScrollView *scrollView = (UIScrollView *)view;
    
    // 讓UIScrollView滾動(dòng)到最前面
    CGPoint offset = scrollView.contentOffset;
    offset.y = - scrollView.contentInset.top;
    [scrollView setContentOffset:offset animated:YES];
}

LYTopWindow的基本實(shí)現(xiàn)原理就是這樣了,如果想看了解基本實(shí)現(xiàn)步驟可以查看LYTopWindow的源代碼:https://github.com/DeveloperLY/LYTopWindow 。

三、補(bǔ)充

如果使用了自定義控件實(shí)現(xiàn)了點(diǎn)擊狀態(tài)欄滾動(dòng)視圖自動(dòng)滾回頂部,那么控制器的這兩個(gè)方法會(huì)失效
1.控制器的- (BOOL)prefersStatusBarHidden方法決定狀態(tài)欄的可見性
2.控制器的- (UIStatusBarStyle)preferredStatusBarStyle方法決定狀態(tài)欄的樣式
原因就是狀態(tài)欄的樣式\可見性,由最頂層(蓋在最上面)window的控制器來決定
所以如果使用了LYTopWindwo框架的,如果要控制狀態(tài)欄的樣式和可見性可以使用下面的代碼:

// 可見性
    [LYTopWindow sharedTopWindow].statusBarHidden = NO;
    [LYTopWindow sharedTopWindow].statusBarHidden = YES;
    
    // 樣式
    [LYTopWindow sharedTopWindow].statusBarStyle = UIStatusBarStyleDefault;
    [LYTopWindow sharedTopWindow].statusBarStyle = UIStatusBarStyleLightContent;
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容