你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發基礎 >> 解決點擊狀態欄時ScrollView自動滾動到初始位置失效辦法

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

編輯:IOS開發基礎

first-aid-850489_640.jpg

作者:維尼的小熊 授權本站轉載。

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

1.gif

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

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

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

2.gif

當控制器內有多個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
    }   
}

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

  func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
      // 添加頂部的window
      TopWindow.showTopWindow()
      return true
  }

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

objective-c代碼

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

#import @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]] && [subview isShowingOnKeyWindow]) {
          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;
}

完成的效果

3.gif

歡迎關注我的技術博客

我的新浪微博

  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved