之前做一个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
相关的代码加一个前缀来与系统的区分一下即可。