解決點擊狀態欄時ScrollView自動滾動到初始位置失效辦法

AmewsAdeve 8年前發布 | 11K 次閱讀 iOS開發 C/C++開發 ScrollView

來自: http://www.jianshu.com/p/836cdd481982

相信細心的開發者都會發現scrollView自帶一個功能,當用戶點擊頂部的狀態欄時,scrollView的ContentOffset.y軸會自動滾動到初始位置,效果如圖所示:

單個scrollView單擊頂部狀態欄系統自帶功能展示

這個功能對用戶來說非常實用,尤其是在scrollView( TableView, WebView, CollectionView一切繼承scrollView的控件 )展示的內容很多,當用戶翻看很久以后,想回到最頂部時,只需單擊一下頂部的狀態欄位置就可以輕松返回到頂部( 這里吐槽下.貌似很多用戶都不知道有這個功能 ),而不用使勁用手滑動到頂部.

可是功能在當前控制器有多個scrollView( TableView, WebView, CollectionView一切繼承scrollView的控件 )的時候就會失效,效果如下圖所示:

當控制器內有多個scrollView時,系統自帶的滾動到頂的功能就會失效

  • 如圖所示,一旦有多個scrollView時,系統自帶的方法就失效了

實際開發中,我們的產品在同一個控制器經常會有多個scrollView組合在一起的情況,這就意味著系統的方法已經失效了,需要開發人員自己來實現這個效果,下面我們就來搞定這個需求

我們分析下原生的方法為什么會失效,當一個控制器內只有一個scrollView時,點擊狀態欄,系統會遍歷當前keyWindow的子控件,發現子控件中只有一個scrollView會調用這個scrollView的setContentOffset: animated:的這個方法,將scrollView的contentOffset.y值修改為初始值,但是當子控件中又多個scrollView時,系統會不知道掉用哪一個scrollView而失效,知道這點我們就知道該如何搞定這個問題了

這里就直接將解決思路一一寫出來不將代碼分段展示了,在代碼中我加了詳細的注釋 objective-c的套路和swift基本一樣 ,在最后會將Swift和objective-c的代碼一起放上,如果需要直接解決問題的童鞋可以直接將代碼拷貝到工程里即可

  • 首先創建一個topWindow繼承至NSObject,這里我們考慮將這個功能完全封裝起來,所以所有的方法都用的類方法,所以用最基本的類就可以
  • 在initialize方法中初始化topWIndow,將topWIndow的級別改成最高的UIWindowLevelAlert級別,設置topWindow位置,并且添加點擊手勢
  • 在topWIndow被點擊調用的方法中,我們拿出UIApplication的keyWindow,遍歷keyWindow的所有子控件,如果滿足是scrollView同時又顯示在當前keyWindow條件時,將subView的contentOffset的y值回復到原始
  • 然后采用遞歸的套路在遍歷subView內時候有滿足條件的子控件,直到沒有滿足條件時會停止

Swift的代碼

import UIKit

class TopWindow: UIWindow {

private static let window_: UIWindow = UIWindow()

///  類初始化方法,保證window_只被創建一次
override class func initialize() {
    window_.frame = CGRectMake(0, 0, global.appWidth, 20)
    window_.windowLevel = UIWindowLevelAlert
    window_.addGestureRecognizer(UITapGestureRecognizer(target: self, action: "topWindowClick"))
}

class func topWindowClick() {
    // 遍歷當前主窗口所有view,將滿足條件的scrollView滾動回原位
    searchAllowScrollViewInView(UIApplication.sharedApplication().keyWindow!)
}

private class func searchAllowScrollViewInView(superView: UIView) {

    for subview: UIView in superView.subviews as! [UIView] {

        if subview.isKindOfClass(UIScrollView.self) && superView.viewIsInKeyWindow() {
            // 拿到scrollView的contentOffset
            var offest = (subview as! UIScrollView).contentOffset
            // 將offest的y軸還原成最開始的值
            offest.y = -(subview as! UIScrollView).contentInset.top
            // 重新設置scrollView的內容
            (subview as! UIScrollView).setContentOffset(offest, animated: true)
        }
        // 遞歸,讓子控件再次調用這個方法判斷時候還有滿足條件的view
        searchAllowScrollViewInView(subview)
    }
}

///  添加topWindow,使手勢生效
class func showTopWindow() {
    window_.hidden = false
}

///  隱藏topWindow,移除手勢
class func hiddenTopWindow() {
    window_.hidden = true
}

}

/// 對UIView的一個擴展 extension UIView {

///  判斷調用方法的view是否在keyWindow中
func viewIsInKeyWindow() -> Bool {
    let keyWindow = UIApplication.sharedApplication().keyWindow!

    // 將當前view的坐標系轉換到window.bounds
    let viewNewFrame = keyWindow.convertRect(self.frame, fromView: self.superview)
    let keyWindowBounds = keyWindow.bounds

    // 判斷當前view是否在keyWindow的范圍內
    let isIntersects = CGRectIntersectsRect(viewNewFrame, keyWindowBounds)

    // 判斷是否滿足所有條件
    return !self.hidden && self.alpha > 0.01 && self.window == keyWindow && isIntersects
}   

}</pre>

  • 在AppDelegate里,程序啟動完成方法時添加就OK了

      func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {

      // 添加頂部的window
      TopWindow.showTopWindow()
    
      return true
    

    }</pre> </li>

  • 需要注意添加了自定義的window后,控制器的改變狀態欄狀態方法會失效,可以在info.plist中將改變狀態欄的管理權交給UIApplication解決,或者在需要改變狀態欄的控制器中調用 TopWindow.hiddenTopWindow() 即可,或者直接改info.plist,用 UIApplication.sharedApplication().setStatusBarStyle來管理
  • </ul>

    objective-c代碼

    • .h文件只暴露顯示和隱藏方法

      #import <Foundation/Foundation.h>
      @interface WNXTopWindow : NSObject
      + (void)show;
      + (void)hide;
      @end
    • .m文件

      #import "WNXTopWindow.h"
      @implementation WNXTopWindow
      static UIWindow *window_;
      //初始化window
      + (void)initialize {
        window_ = [[UIWindow alloc] init];
        window_.frame = CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, 20);
        window_.windowLevel = UIWindowLevelAlert;
        [window_ addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(windowClick)]];
      }  
      + (void)show {
        window_.hidden = NO;
      }
      + (void)hide {
        window_.hidden = YES;
      }
      // 監聽窗口點擊
      + (void)windowClick {
        UIWindow *window = [UIApplication sharedApplication].keyWindow;
        [self searchScrollViewInView:window];
      }
      + (void)searchScrollViewInView:(UIView *)superview {
        for (UIScrollView *subview in superview.subviews) {
            // 如果是scrollview, 滾動最頂部
            if ([subview isKindOfClass:[UIScrollView class]] && [self isShowingOnKeyWindow: subView]) {
                CGPoint offset = subview.contentOffset;
                offset.y = - subview.contentInset.top;
                [subview setContentOffset:offset animated:YES];
            }
      
            // 遞歸繼續查找子控件
            [self searchScrollViewInView:subview];
        }
      }
      + (BOOL)isShowingOnKeyWindow:(UIView *)view {
        // 主窗口
        UIWindow *keyWindow = [UIApplication sharedApplication].keyWindow;
        // 以主窗口左上角為坐標原點, 計算self的矩形框
        CGRect newFrame = [keyWindow convertRect:view.frame fromView:view.superview];
        CGRect winBounds = keyWindow.bounds;
        // 主窗口的bounds 和 self的矩形框 是否有重疊
        BOOL intersects = CGRectIntersectsRect(newFrame, winBounds);
      
        return !view.isHidden && view.alpha > 0.01 && view.window == keyWindow && intersects;
      }
      @end
    • 同樣,也是在程序初始化完成AppDelegate文件中顯示topWindow,整個工程這個問題就統統解決了

      - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
        // 添加一個window, 點擊這個window, 可以讓屏幕上的scrollView滾到最頂部
        [WNXTopWindow show];
      
        return YES;
      }

      針對iOS9.0會運行程序崩潰的解決辦法

    • 更新了下Xcode7.0,發現運行程序會崩潰,解決方法是給頂部的topWindow添加一個rootViewController,創建一個topVC繼承至UIViewController,將topVC設置為頂部自定義的window的跟控制器,將原本點擊事件放到topVC的touchBegin方法中即可

    完成的效果

    完成效果

    </div>

    歡迎關注我的技術博客

    點擊鏈接我的博客

    我的新浪微博

    我的新浪微博

    歡迎朋友關注,如果有什么意見和問題關注留言即可, 本文隨意轉載,請注明作者出處

    </div>

     本文由用戶 AmewsAdeve 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
     轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
     本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!