之前做一個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
相關的代碼加一個前綴來與系統的區分一下即可。