LazyScrollView的特殊使用情況

之前做一個UITableView流水佈局的需求的時候歪打正着的看到了這個庫,使我免去了重複造輪子的窘境。

首先我做了一個兜底方案是直接算出所有的frame,直接往一個大Cell上添加。當然,這種方案在數據源變化的情況下有點噁心。

自然而然我就想到了我自己的帶緩存池的庫-ZCHScrollChannelView。後面由於慣性思維跳了一個坑,這個後面再說。

我們組的安卓哥們給我推薦了一下阿里巴巴的Tangram庫,這個庫本身可以分爲iOS和安卓兩個庫。然後我在研究這個庫的時候發現它的緩存池佈局控件是LazyScrollView,當然,也支持pod集成。

///PS,安裝這個CocoaPods庫會帶入一個天貓的UI分類操作的庫進來:`TMUtils`,
///不過可以放心的是這兩個庫都沒有進行任何的網絡請求,
///也就是說,這是一個離線庫(本地庫)
pod 'LazyScroll'

框架情況
在這裏插入圖片描述

試用的一些Demo有的情況我就不贅述了,對於我們度小滿理財的這個項目來說,有點特殊因爲基本上所有的cell,item,view都會經過一個類來解析後臺的模板號,來確定使用哪個模板,而且這個流水佈局需要放置到任何地方,所以,Demo裏頭的放置到footer上是不行的(放置到footer上是不停更新footer的高度來代替“刷新”)。

當外層有scrollView及其子類的時候,可以設置LazyScroll的outerScrollView的屬性,LazyScroll內部會自動監聽外部ScrollView的滾動,然後根據UIKit的方法換算LazyScroll這個控件的顯示範圍,如果大家有越獄設備,可以使用“Reveal”查看一下天貓首頁的流水佈局或者是淘寶首頁的流水佈局。它會有一個特別高的lazyScroll控件,屏幕內的控件顯示,屏幕外的控件不會顯示。

> 這裏先留個空,回家截個圖到這裏來。
這就是這個流水佈局的部分了.如果想看這個部件的contentOffset有多高的話,可以看一下下圖

在這裏插入圖片描述

上面也說了,我們這裏是有特殊情況的。cell不停更新高度的話就需要外層不停的reload,這樣體驗就特別差了。所以和我兜底方案也一樣,每次提前算出cell的高度,高度就儘量少動,一次刷新tableview,內部就可以reload。

outerScrollView的設置

與MJRefresh需要監聽父控件"UIScrollVIew及其子類"的滾動是一個道理,LazyScrollView也需要監聽父控件的滾動來間接“獲取”LazyScrollView應該的“可視區域”。具體怎麼做的可以參考我這一篇文章MJRefresh研究

上面說了,我們這裏是UITableViewCell,所以和Demo的outerScrollView的設置會有些不一樣。

在這裏插入圖片描述

既然原理是一樣(KVO監聽父控件屬性,具體可以戳這篇文章:MJRefresh研究),那麼當視圖生命週期結束,outerScrollView也得被移除(置爲nil),下面是這個屬性當說明。

/**
 LazyScrollView can be used as a subview of another ScrollView.
 For example:
 You can use LazyScrollView as footerView of TableView.
 Then the outerScrollView should be that TableView.
 You MUST set this property to nil before the outerScrollView's dealloc.
 */
@property (nonatomic, weak, nullable) UIScrollView *outerScrollView;

1.在cellforRow設置

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableView *cell = [tableView dequeueReusableCellWithIdentifier:@"標誌符"];
    if (cell == nil) {
        cell = [[FlowLayoutCell alloc] initWithStyle:(UITableViewCellStyleDefault) reuseIdentifier:@"標誌符"];
    }
    if ([cell isKindOfClass:[flowLayoutClass class]]) {
    	cell.flowLayoutView.outerScrollView = tableView;
    }
}

2.通過生命週期來設置

- (void)willMoveToSuperview:(UIView *)newSuperview {
    [super willMoveToSuperview:newSuperview];
    if ([newSuperview isKindOfClass:[UITableView class]]) {
        self.flowLayoutView.outerScrollView = newSuperview;
    }
}

這個2.方法是不是看起來更好一些,更沒有侵入性一些。不過,這麼寫是有問題的。
- (void)willMoveToSuperview:(UIView *)newSuperview會在兩個時機調用,一個是addSubView,將要把視圖添加上去的時候。還有個一個是willMoveToSuperView,將要把視圖移除除去的時候,區別是,add的時候newSuperview是有值的,移除的時候newSuperview的值是nil。

在iOS7-iOS11中。cell的superView並不直接是UITableView。而是被添加到了一個[id class]後得出的是UITableViewWrapperView類型的.通過下面的代碼我們可以得知這個類是UIScollView的子類

UIView *wrapperView = [[NSClassFromString(@"UITableViewWrapperView") alloc] init];
if ([wrapperView isKindOfClass:[UIScrollView class]]) {
	NSLog(@"scrollView");
} else if ([wrapperView isKindOfClass:[UITableView class]]) {
	NSLog(@"tableView");
} else if ([wrapperView isKindOfClass:[UICollectionView class]]) {
	NSLog(@"collectionView");
}

所以這段代碼可以改成:

- (void)willMoveToSuperview:(UIView *)newSuperview {
    [super willMoveToSuperview:newSuperview];
    if ([newSuperview isKindOfClass:[UITableView class]]) {
        self.flowLayoutView.outerScrollView = newSuperview;
    } else if ([newSuperview isKindOfClass:[NSClassFromString(@"UITableViewWrapperView")]]) {
		// 獲取它的下一個響應者
		self.flowLayoutView.outerScrollView = newSuperview.nextResponder;
	}
}

3.第二種方法的改版

便利一下獲取到[UITableView class]然後設置到outerScrollView上,因爲我這個需求是已經確定UITableViewCell上了。所以可以這麼幹,不是非常通用的,例如上面提到的UIScrollView的子類UITableViewWrapperView

因爲是Cell內做流水佈局,所以儘量要將tableView的reloadData的次數減少來減少抖動。所以我的方案是在yy_modelWithJson:的時候就把佈局計算完畢,需要多高直接在heightForRow的時候直接返回。然後就是簡單的使用lazyScroll的datasource方法了。

額外的坑

有三點坑需要注意。1.autoAddSubview屬性,這個屬性需要設置爲yes,不然reloadData的時候是不會顯示視圖的,這是我上面提到的慣性思維導致的坑的第一點。2.lazyScrollView調用reloadData之前需要調用clearVisibleItems:並且傳入YES,不然屏幕上已經顯示的View是不會消失的。

第三點我特意提出來講,看得出來lazyScroll想要儘量的模仿系統的API。例如

/// TMLazyScrollView.h
/**
 Get reuseable item view by reuseIdentifier.
 */
- (nullable UIView *)dequeueReusableItemWithIdentifier:(nonnull NSString *)identifier;
/**
 Get reuseable item view by reuseIdentifier and muiID.
 MuiID has higher priority.
 */
- (nullable UIView *)dequeueReusableItemWithIdentifier:(nonnull NSString *)identifier
                                                 muiID:(nullable NSString *)muiID;

reuseIdentifier & muiID

// UIView + TMLazyScrollView
@property (nonatomic, copy) NSString *muiID;
@property (nonatomic, copy) NSString *reuseIdentifier;

- (instancetype)initWithReuseIdentifier:(NSString *)reuseIdentifier;
- (instancetype)initWithFrame:(CGRect)frame reuseIdentifier:(NSString *)reuseIdentifier;

坑點就是這個reuseIdentifier導致的,大家也知道,UITableViewCell與UICollectionViewCell都具有這個屬性,如果想要通用性的話用UICollectionViewCell來當重用的UIView是會取不到控件的。因爲reuseIdentifier取的話一直爲nil。如果一定要用的話,我建議把這個庫下載再來,把reuseIdentifier以及reuseIdentifier相關的代碼加一個前綴來與系統的區分一下即可。

發佈了271 篇原創文章 · 獲贊 60 · 訪問量 21萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章