iOS-關于UIScrollView的嵌套聯動

基本場景

(最終效果和鏈接在文末,支持Swift與OC)
UIScrollView嵌套多個UITableView的場景在APP里很常見,復雜點還有各種UITableView、UICollectionView各種嵌套的場景,目前通用的解決辦法基本是在UIScrollView的代理方法
- (void)scrollViewDidScroll:(UIScrollView *)scrollView里比較偏移量和需要懸停的坐標位置再做相應處理,定義主要父視圖scrollViewmainScrollView,嵌套的多個聯動scrollViewcontentScrollView,先總結下大致思路。

  1. 手勢響應,shouldRecognizeSimultaneouslyWithGestureRecognizer必須同時作用于mainScrollView和所有contentScrollView,而contentScrollView是需要橫向滑動的,因此要允許同時垂直滑動,而不支持水平和垂直同時滑動。
  2. mainScrollView 、contentScrollView均需要實現scrollViewDidScroll并分別處理,兩者的實際滑動是互斥的,同一時刻只有一方需要響應滑動,另一方做懸停處理,相互通知也很是麻煩。
  3. 下拉刷新,mainScrollView 、contentScrollView各自有著需要下拉刷新的場景,一般contentScrollView需要下拉刷新時也正好處于自身臨界固定點的位置,這里也需要單獨處理下。
  4. scrollsToTop 這其實是一個很容易被忽略的點,iOS系統有個小的隱藏功能,點擊系統狀態欄會查找到當前顯示的UIScrollView并響應回到頂部,而在這種嵌套的場景里,主次需要響應的時機就依賴于需求了,或許需求就要求先回到contentScrollView后回到mainScrollView的頂部呢??。

要把這些都處理好,寫代碼的時候必須梳理清楚,即便如此,當項目不同模塊都有著類似的需求的時候,又得好好捋一遍了,可能相似而不相同,一不小心就容易一團麻,令人抓狂。

之前在網上搜索這類需求的方案,大部分都是上述的大概思路,其他有些是對整個相關UI層的封裝,一來學習使用成本略高,二則在已經成型的項目里使用的話,改動略大,耦合性比較高。于是打算自己重新整理一個低耦合的方案出來。

結果方案

依然是比較偏移量處理懸停,為了減少耦合,因此不走代理,采用KVO的方式監測偏移量,初始化需要設置mainScrollView和各contentScrollView,考慮到不少子頁面可能存在懶加載的情況,因此contentScrollView可以不必在初始化時全給到,可延后等待時機添加。index參數用于標記contentScrollView在其橫向父scrollView的位置,避免受到其他兄弟視圖的滑動影響。

+ (instancetype)managerWithMainScrollView:(UIScrollView *)mainScrollView contentScrollViews:(NSArray<UIScrollView *> *_Nullable)contentScrollViews;
- (void)addContentScrollView:(UIScrollView *)contentScrollView withIndex:(NSInteger)index;

初始化完了,接下來就是本方案中唯一的必設屬性了:

@property (nonatomic) CGFloat contentScrollDistance;

mainScrollView懸停相關的值,contentScrollView可以在mainScrollView移動的距離,一般是需要顯示的內容區域在mainScrollView的相對坐標Y值,如圖所示,箭頭是終點,圖中上面高為300,只要設置contentScrollDistance為300,就可以基本實現完整的嵌套聯動了。當頁面刷新高度變化的時候,只需要重新調整contentScrollDistance的值即可。

必設屬性之后就是擴展需求的可選屬性了。

///各contentScrollView的共同橫向superScrollView
///內部是尋找第一個contentScrollView的父視圖里的第一個UIScrollView
///與實際不符時可 以此修正
///主要用于scrollsToTop及散裝屬性
@property (nonatomic, weak) UIScrollView *fixHorizontalSuperScrollView;
///滑動條顯示 默認切換顯示
@property (nonatomic) XShowIndicatorType showIndicatorType;
///默認main可下拉
@property (nonatomic) XMixScrollPullType mixScrollPullType;
///點擊狀態欄回頂部時  是否直接回到mainScrollView頂部 默認Yes
@property (nonatomic) BOOL scrollsToMainTop;

///是否開啟動態模擬 默認 NO  在main范圍內content范圍外 上拉沒有過度滑動效果 YES則添加模擬效果
@property (nonatomic) BOOL enableDynamicSimulate;
///動態模擬過度滑動效果 阻力參數 默認 2
@property (nonatomic) CGFloat dynamicResistance;
  1. 如注釋所示,該屬性的出現主要是為了scrollsToTop的切換以及接下來要介紹的散裝屬性。
  2. mainScrollViewcontentScrollView各有各的滑動條,簡單暴力的話就是全隱藏,但是畢竟contentScrollView可能上拉加載更多無限長,還是需要看情況顯示的。
  3. 下拉刷新,可以自由設置mainScrollViewcontentScrollView是否支持下拉刷新。
  4. scrollsToMainTopNO時,點擊狀態欄會優先使當前contentScrollView回到頂部,其次回到mainScrollView頂部。
  5. 關于動態模擬,在滑動contentScrollView區域外的mainScrollView時,contentScrollView不會響應手勢,自然也不會滑動,在慣性滑動過渡到contentScrollView的時候mainScrollView由于懸停設置會導致瞬停,沒法好好平滑過渡,最終參考網上動態模擬的方案針對上滑觸摸點在contentScrollView區域外mainScrollView區域內的單個場景增加了慣性模擬。因為需要額外的計算且不是必須的,所以默認關閉了。

以上關于contentScrollView的設置都是針對所有內容視圖的,考慮到不同contentScrollView可能有著不同需求,比如有的子頁面內容較少不需要顯示滑動進度條,不需要回到子頁面頂部,有的子頁面內容可以無限上拉加載更多,需要進度條也需要回到子頁面頂部之類的。因此增加了部分可選屬性單獨設置的方法。

///開啟散裝屬性 默認NO
@property (nonatomic) BOOL enableCustomConfig;

- (void)setShowIndicatorType:(XShowIndicatorType)showIndicatorType forScrollView:(UIScrollView *)contentScrollView;
- (void)setMixScrollPullType:(XMixScrollPullType)mixScrollPullType forScrollView:(UIScrollView *)contentScrollView;
- (void)setScrollsToMainTop:(BOOL)scrollsToMainTop forScrollView:(UIScrollView *)contentScrollView;
- (void)setEnableDynamicSimulate:(BOOL)enableDynamicSimulate forScrollView:(UIScrollView *)contentScrollView;

沒有單獨設置屬性的contentScrollView依然以主要設置為準。

大致實現

KVO那里判斷代碼比較長,大致說一下,KVO里在
mainScrollView 、contentScrollView的常規嵌套聯動處理的基礎上,加上了回到頂部、是否顯示下拉狀態的處理、以及慣性模擬的判斷調用,此外對內容視圖橫向父scrollView的偏移量也添加了觀察(如下),內容視圖切換時需要校準scrollsToTop狀態以及對散裝進度條的顯示狀況進行修正。.p的寫法只是為了少寫幾個associatedObject

  //橫向父scrollView滑動處理
  NSInteger index = scrollView.contentOffset.x / scrollView.frame.size.width;
  if (scrollView.p.index != index) {
    scrollView.p.index = index;
    self.currentIndex = index;
    [self checkScrollsToTop];
    [self checkCustomConfig];
  }

聯動的滑動過渡如下

- (void)changeMainScrollStatus:(BOOL)mainCanScroll
{
    if (self.mainScrollView.p.canScroll == mainCanScroll) {
        return;
    }
    self.mainScrollView.scrollsToTop = YES;
    self.mainScrollView.p.canScroll = mainCanScroll;
    for (UIScrollView *contentScrollView in self.contentScrollViews) {
        contentScrollView.p.canScroll = !mainCanScroll;
        if (mainCanScroll) {
            contentScrollView.contentOffset = CGPointZero;
        }
        if (!self.scrollsToMainTop) {
            contentScrollView.scrollsToTop = !mainCanScroll;
        }
    }
}

這里是到臨界點過渡時的處理,canScroll = YES代表著主動滑動,反之則是懸停,被動跟滑,當mainScrollView可以滑動的時候重置下contentScrollView的偏移量。mainScrollView.scrollsToTop = YES則是因為在正好臨界點時如果為NO則無法回到頂部,mainScrollView的實際scrollsToTop值會在KVO contentScrollView的偏移量大于0時重新賦值。

關于散裝屬性的處理比較簡單,用字典存值,重寫了屬性的get方法。

最后是關于UIScrollView分類實現的這兩個方法

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
    if (self.p.markScroll) {
        //阻止橫豎聯動
        UIScrollView *scrollView = (UIScrollView *)otherGestureRecognizer.view;
        if ([scrollView isKindOfClass:[UIScrollView class]] && scrollView.p.markScroll) {
            return YES;
        }
    }
    //阻止其他意外聯動
    return NO;
}

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
    if (self.p.scrollManager.enableDynamicSimulate) {
        [self.property.scrollManager.dynamicSimulate stop];
        if (self.p.isMain) {
            XMixScrollManager *scrollManager = self.p.scrollManager;
                scrollManager.isTouchMain = point.y < scrollManager.contentScrollDistance;
        }
    }
    return [super pointInside:point withEvent:event];
}

pointInside的處理,一是記錄是否在需要模擬的坐標區間內滑動,二是停止之前的模擬。動態模擬本身就不多說了,想要了解的可以看文末的鏈接。

部分效果

簡單UIScrollView嵌套
UITableView嵌套

鏈接

動態模擬部分參考->https://www.tuicool.com/articles/QVJnAbB
完整代碼地址->XMixScrollManager
Swift版代碼地址->XMixScrollManager_swift

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,030評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,310評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,951評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,796評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,566評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,055評論 1 322
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,142評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,303評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,799評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,683評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,899評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,409評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,135評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,520評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,757評論 1 282
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,528評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,844評論 2 372

推薦閱讀更多精彩內容

  • 在iOS中,滾動視圖UIScrollView用于查看大于屏幕的內容。Scroll View有兩個主要目的: 讓用戶...
    pro648閱讀 38,329評論 4 37
  • 前言 在上一篇文章中,我們學習了三方刷新庫MJRefresh(巧用MJRefresh),同時我們也說了MJRefr...
    langkee閱讀 15,962評論 4 22
  • 一、簡介 <<繼承關系:UIScrollView --> UIView-->UIResponder-->NSObj...
    無邪8閱讀 1,880評論 0 0
  • 1.UIScrollView是什么? 移動設備的屏幕?小是極其有限的,因此直接展?在用戶眼前的內容也相當有限,當展...
    happycolt閱讀 10,508評論 1 16
  • 大眾對八字風水的態度 信與不信的 有興趣的沒興趣的 想學習的不想學只想算算自己的 想借此探索自己命運走勢的 想學會...
    028b47fc08dc閱讀 302評論 0 0