测试已测试 | ✗ |
语言语言选项 | Obj-CObjective C |
许可证 | MIT |
发布最新发行 | 2017年12月 |
由 qiaomu,halfrost,Assuner-Lee,EyreFree 维护。
依赖关系 | |
ReactiveObjC | >= 0 |
Masonry | >= 0 |
LPDAdditionsKit | >= 0 |
要运行示例项目,请克隆仓库,并在 Example 目录中首先运行 pod install
。
对 ReactiveCocoa 非常了解,也可以参阅图解 ReactiveCocoa 基本函数
LPDTableViewKit 可以通过 CocoaPods 获得。要安装它,只需将以下行添加到您的 Podfile 中
pod "LPDTableViewKit"
foxsofter, [email protected]
LPDTableViewKit 可在 MIT 许可证下获得。有关更多信息,请参阅 LICENSE 文件。
##UITableView改造之路
Cocoa Touch 框架无疑是一个很好的框架,尤其是对动画的支持,在我接触过的框架中可能是最好的(当然我接触的框架可能比较少),但是就UITableView来说确实存在很多可吐槽的地方,从我个人理解的角度做一些分析,尝试解决这些可吐槽的地方,并提供解决方案。
枚举一直是为了可扩展而存在的,UITableView 中对 UITableViewStyle 的使用可谓滥用了,先看看这个枚举的定义,枚举项的命名不够直观,源码的注释也得不到有效信息,
typedef NS_ENUM(NSInteger, UITableViewStyle) {
UITableViewStylePlain, // regular table view
UITableViewStyleGrouped // preferences style table view
};
再看看如下文档的说明,基本明确了设计者的本意,UITableViewStyle想要区分的是页眉或页脚(section headers or footers)是否浮动,接下来做一些解析:
case plain
A plain table view. Any section headers or footers are displayed as inline separators and float when the table view is scrolled.
case grouped
A table view whose sections present distinct groups of rows. The section headers and footers do not float.
UITableView 的初始化函数
- (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style; // must specify style at creation. -initWithFrame: calls this with UITableViewStylePlain
综上得出结论:UITableViewStyle 是不该用的。
UITableViewCell 存在很多枚举的乱用,乱用表示不该用的时候用了。
typedef NS_ENUM(NSInteger, UITableViewCellStyle) {
UITableViewCellStyleDefault, // Simple cell with text label and optional image view (behavior of UITableViewCell in iPhoneOS 2.x)
UITableViewCellStyleValue1, // Left aligned label on left and right aligned label on right with blue text (Used in Settings)
UITableViewCellStyleValue2, // Right aligned label on left with blue text and left aligned label on right (Used in Phone/Contacts)
UITableViewCellStyleSubtitle // Left aligned label on top and left aligned label on bottom with gray text (Used in iPod).
};
UITableViewCell 的初始化方法中也带上了 UITableViewCellStyle,先看代码
// Designated initializer. If the cell can be reused, you must pass in a reuse identifier. You should use the same reuse identifier for all cells of the same form.
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(nullable NSString *)reuseIdentifier;
如果说 UITableViewDelegate 设计者觉得就只存在两种 style,那么 UITableViewCell 设计中加入 UITableViewCellStyle 就显得完全是乱用了。一样的道理,枚举从来就不是为了扩展而存在,UITableViewCellStyle 作为 cell 的基类,扩展是必须的,不可能所有的 cell 都长得跟 UITableViewCellStyle 中定义的几个枚举项所分类的完全一样,所以这个设计是有多恶心啊。
再看看 UITableViewCellStyle 的各个枚举项的命名,简直是残暴啊,UITableViewCellStyleValue1,UITableViewCellStyleValue2 这些是什么鬼哦,再看看注释,分别说明 Used in Settings 和 Used in Phone/Contacts,这很明显了,这些实现完全就是系统组件用到了这样的实现,然后直接作为 api 开放出来的,并没有做很好的抽象,在初始化函数中加入 UITableViewCellStyle,污染了初始化函数,限制了扩展,每当在写一个 UITableViewCell 的子类时,总是有一种莫名的哀伤,UITableViewCellStyle 作为参数存在唯一的作用就是多写了点代码,然后没有任何意义。这些 cell style 所表示的 cell 完全应该通过子类化来实现的,所以 UITableViewCellStyle 的乱用是有点惨不忍睹的。
typedef NS_ENUM(NSInteger, UITableViewCellSeparatorStyle) {
UITableViewCellSeparatorStyleNone,
UITableViewCellSeparatorStyleSingleLine,
UITableViewCellSeparatorStyleSingleLineEtched // This separator style is only supported for grouped style table views currently
};
无论如何也不应该存在这样一个枚举,CellSeparatorStyle是为不同UITableViewStyle设计的,不管何种样式,应该只需要一个isShowCellSeparatorLine的BOOL值来表示是否需要显示分隔线,如果是UITableViewStyleGrouped样式,可能需要一个额外的isCellSeparatorLineEtched,如果在前面的假设中页眉或页脚都是默认浮动的,这种设计是很合理的。
当一个枚举的命名过于混乱时,该枚举的存在实际上需要考虑,因此UITableViewCellSeparatorStyle也是典型的误用。
typedef NS_ENUM(NSInteger, UITableViewCellSelectionStyle) {
UITableViewCellSelectionStyleNone,
UITableViewCellSelectionStyleBlue,
UITableViewCellSelectionStyleGray,
UITableViewCellSelectionStyleDefault NS_ENUM_AVAILABLE_IOS(7_0)
};
UITableViewCellSelectionStyle旨在表示cell的选择样式,这里可能通过这种方式来增加几种默认值,因为CellSelectionStyle还可以定制,但是UITableViewCellSelectionStyleDefault放在最后,UITableViewCellSelectionStyleNone放在最开始,到底哪个才是默认值呢?
typedef NS_ENUM(NSInteger, UITableViewCellFocusStyle) {
UITableViewCellFocusStyleDefault,
UITableViewCellFocusStyleCustom
} NS_ENUM_AVAILABLE_IOS(9_0);
UITableViewCellFocusStyle枚举的存在难道只是为了无病呻吟吗?
UITableViewDelegate、UITableViewDataSource,包括刚刚引入的UITableViewDataSourcePrefetching,这几个委托的设计似乎缺少一些设计,更像是为了解决问题而写入代码,作为一个基础框架,这实在是不合适的。
// Display customization
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath;
- (void)tableView:(UITableView *)tableView willDisplayHeaderView:(UIView *)view forSection:(NSInteger)section NS_AVAILABLE_IOS(6_0);
- (void)tableView:(UITableView *)tableView willDisplayFooterView:(UIView *)view forSection:(NSInteger)section NS_AVAILABLE_IOS(6_0);
- (void)tableView:(UITableView *)tableView didEndDisplayingCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath*)indexPath NS_AVAILABLE_IOS(6_0);
- (void)tableView:(UITableView *)tableView didEndDisplayingHeaderView:(UIView *)view forSection:(NSInteger)section NS_AVAILABLE_IOS(6_0);
- (void)tableView:(UITableView *)tableView didEndDisplayingFooterView:(UIView *)view forSection:(NSInteger)section NS_AVAILABLE_IOS(6_0);
这几个委托函数都与cell、页眉、页脚相关,但都集中在UITableViewDelegate中,且命名类似。一个协议在定义时存在过多的@optional委托函数,这个协议的设计本身就是不合理的,应该拆分成更细致的协议,我们应该在需要的时候选择相应的协议,而不是实现存在的@optional委托函数。然后,UITableViewDelegate这个协议本身所有的委托函数都是@optional,这是真的不合理。如果我们来设计cell、页眉、页脚实际上都是UIView,并且存在许多共同点(参考UICollectionView的设计,cell、页眉、页脚就存在一个共同的基类UICollectionReusableView),应该设计一个UIReusableView(UICollectionReusableView也可以不再需要),其中存在如下方法,这些方法可以在子类中重写。
- (void)willAppear;
- (void)didAppear;
- (void)willDisappear;
- (void)didDisappear
还应该设计一个UIReusableViewDelegate,其中包括如下委托函数
- (void)willAppear:(UIReusableView*)reusableView;
- (void)didAppear:(UIReusableView*)reusableView;
- (void)willDisappear:(UIReusableView*)reusableView;
- (void)didDisappear:(UIReusableView*)reusableView;
UIReusableView有一个UIReusableViewDelegate,前面提到的六个委托函数实际上应该在cell、页眉、页脚各自需要的时候实现UIReusableViewDelegate。
综上所述,UITableViewDelegate实际上是过于沉重。
下面这些委托函数实际上应该存在于UITableViewDataSource中。页眉、页脚的数据源应该与cell的数据源平等,不应该是经常不使用就放到UITableViewDelegate中,本来就应该放在UITableViewDataSource,不常用的可以使用optional修饰。
// Variable height support
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath;
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section;
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section;
// Use the estimatedHeight methods to quickly calcuate guessed values which will allow for fast load times of the table.
// If these methods are implemented, the above -tableView:heightForXXX calls will be deferred until views are ready to be displayed, so more expensive logic can be placed there.
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath NS_AVAILABLE_IOS(7_0);
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForHeaderInSection:(NSInteger)section NS_AVAILABLE_IOS(7_0);
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForFooterInSection:(NSInteger)section NS_AVAILABLE_IOS(7_0);
// Section header & footer information. Views are preferred over title should you decide to provide both
- (nullable UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section; // custom view for header. will be adjusted to default or specified header height
- (nullable UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section; // custom view for footer. will be adjusted to default or specified footer height
通过前面的整理,那么UITableViewDataSource应该包括以下这些函数
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView;
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath;
- (nullable NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section;
- (nullable UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section;
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForHeaderInSection:(NSInteger)section;
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section;
- (nullable NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section;
- (nullable UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section;
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForFooterInSection:(NSInteger)section;
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section;
……
与前面提到的UITableViewDelegate设计之重同理,cell、页眉、页脚的DataSource也应该分开,在需要的时候实现对应的DataSource,需要定义一个额外的枚举UIReusableViewType。
typedef NS_ENUM(NSInteger, UIReusableViewType) {
UIReusableViewTypeNone,
UIReusableViewTypeHeader,
UIReusableViewTypeFooter
};
然后对页眉、页脚就有UIReusableViewDataSource,其中的委托函数如下:
- (nullable NSString *)reusableView:(UIReusableView*)reusableView reusableViewType:(UIReusableViewType)reusableViewType titleInSection:(NSInteger)section;
- (nullable UIView *)reusableView:(UIReusableView*)reusableViewreusableViewType:(UIReusableViewType)reusableViewType viewInSection:(NSInteger)section;
- (CGFloat)reusableView:(UIReusableView*)reusableView reusableViewType:(UIReusableViewType)reusableViewType estimatedHeightInSection:(NSInteger)section;
单独针对cell,有UITableViewCellDataSource,其中的委托函数如下:
- (NSInteger)numberOfSections;
- (NSInteger)numberOfRowsInSection:(NSInteger)section;
- (UITableViewCell *)cellForRowAtIndexPath:(NSIndexPath *)indexPath;
- (CGFloat)heightForRowAtIndexPath:(NSIndexPath *)indexPath;
至于UITableViewDataSourcePrefetching不应该出现,为了优化滚动帧率而拆东墙补西墙的做法。从开发者的角度来看,最简单的方法是将整个数据源提供给UITableView,剩下的就应该是UITableView自身去实现了,因为数据已经有了,想要什么预加载都是框架自身的事情了,减少对开发者的依赖,更是减少api的耦合度,对外暴露的接口越多越不好。
在吐槽时,经常会给出自己认为更合理的设计,然而并没有什么实际效果。既有代码无法修改,那么改造之路在哪里呢?既然不能改变既有代码,那么只能是将这些东西尽可能封装起来。Objective-C语言提供了一些有趣的编译期常量NS_UNAVAILABLE,可以在编译期禁用父类的方法,算是一种不完美中的完美吧。我们可以禁用掉一些不合理的类成员,以达到更好的封装效果。
UITableView可以禁用被枚举污染的初始化函数,重写默认的initWithFrame初始化函数并将默认style设置为UITableViewStyleGrouped,参考类LPDTableView,暂时并没有重写初始化函数,目前认为无伤大雅。
UITableViewCell无法禁用被枚举污染的初始化函数,因为重用时可能会调用到,参考类LPDTableViewCell,选择忽视UITableViewCellStyle,并将现有的几种cellStyle都扩展成对应的子类,LPDTableViewDefaultCell、LPDTableViewValue1Cell、LPDTableViewValue2Cell、LPDTableViewSubtitleCell,命名仍然保持一致,毕竟能习惯这种惨状。
既然无法改造既有的UITableView,可以从另一个方面来解决。
引入MVVM的思想,为UITableView添加对应的ViewModel,有了ViewModel,就可以引入数据驱动的方式,当我们需要为Cell、页眉、页脚提供DataSource时,只需要调用LPDTableViewModelProtocol中的方法即可,接口的粒度已经比较细了,但可能不是最合理的组合,相关的函数如下:
- (nullable NSIndexPath *)indexPathForCellViewModel:(__kindof id<LPDTableCellViewModelProtocol>)cellViewModel;
- (nullable __kindof id<LPDTableCellViewModelProtocol>)cellViewModelFromIndexPath:(NSIndexPath *)indexPath;
- (NSInteger)sectionIndexForHeaderViewModel:(__kindof id<LPDTableHeaderFooterViewModelProtocol>)headerViewModel;
- (nullable __kindof id<LPDTableHeaderFooterViewModelProtocol>)headerViewModelFromSection:(NSInteger)sectionIndex;
- (NSInteger)sectionIndexForFooterViewModel:(__kindof id<LPDTableHeaderFooterViewModelProtocol>)footerViewModel;
- (nullable __kindof id<LPDTableHeaderFooterViewModelProtocol>)footerViewModelFromSection:(NSInteger)sectionIndex;
- (void)addCellViewModel:(__kindof id<LPDTableCellViewModelProtocol>)cellViewModel;
- (void)addCellViewModel:(__kindof id<LPDTableCellViewModelProtocol>)cellViewModel
withRowAnimation:(UITableViewRowAnimation)animation;
- (void)addCellViewModel:(__kindof id<LPDTableCellViewModelProtocol>)cellViewModel toSection:(NSUInteger)sectionIndex;
- (void)addCellViewModel:(__kindof id<LPDTableCellViewModelProtocol>)cellViewModel
toSection:(NSUInteger)sectionIndex
withRowAnimation:(UITableViewRowAnimation)animation;
- (void)addCellViewModels:(NSArray<__kindof id<LPDTableCellViewModelProtocol>> *)cellViewModels;
- (void)addCellViewModels:(NSArray<__kindof id<LPDTableCellViewModelProtocol>> *)cellViewModels
withRowAnimation:(UITableViewRowAnimation)animation;
- (void)addCellViewModels:(NSArray<__kindof id<LPDTableCellViewModelProtocol>> *)cellViewModels
toSection:(NSUInteger)sectionIndex;
- (void)addCellViewModels:(NSArray<__kindof id<LPDTableCellViewModelProtocol>> *)cellViewModels
toSection:(NSUInteger)sectionIndex
withRowAnimation:(UITableViewRowAnimation)animation;
- (void)insertCellViewModel:(__kindof id<LPDTableCellViewModelProtocol>)cellViewModel atIndex:(NSUInteger)index;
- (void)insertCellViewModel:(__kindof id<LPDTableCellViewModelProtocol>)cellViewModel
atIndex:(NSUInteger)index
withRowAnimation:(UITableViewRowAnimation)animation;
- (void)insertCellViewModel:(__kindof id<LPDTableCellViewModelProtocol>)cellViewModel
atIndex:(NSUInteger)index
inSection:(NSUInteger)sectionIndex;
- (void)insertCellViewModel:(__kindof id<LPDTableCellViewModelProtocol>)cellViewModel
atIndex:(NSUInteger)index
inSection:(NSUInteger)sectionIndex
withRowAnimation:(UITableViewRowAnimation)animation;
- (void)insertCellViewModels:(NSArray<__kindof id<LPDTableCellViewModelProtocol>> *)cellViewModels
atIndex:(NSUInteger)index;
- (void)insertCellViewModels:(NSArray<__kindof id<LPDTableCellViewModelProtocol>> *)cellViewModels
atIndex:(NSUInteger)index
withRowAnimation:(UITableViewRowAnimation)animation;
- (void)insertCellViewModels:(NSArray<__kindof id<LPDTableCellViewModelProtocol>> *)cellViewModels
atIndex:(NSUInteger)index
inSection:(NSUInteger)sectionIndex;
- (void)insertCellViewModels:(NSArray<__kindof id<LPDTableCellViewModelProtocol>> *)cellViewModels
atIndex:(NSUInteger)index
withAnimation:(UITableViewRowAnimation)animation;
- (void)insertCellViewModels:(NSArray<__kindof id<LPDTableCellViewModelProtocol>> *)cellViewModels
atIndex:(NSUInteger)index
inSection:(NSUInteger)sectionIndex
withRowAnimation:(UITableViewRowAnimation)animation;
- (void)reloadCellViewModelAtIndex:(NSUInteger)index inSection:(NSInteger)sectionIndex;
- (void)reloadCellViewModelAtIndex:(NSUInteger)index
inSection:(NSInteger)sectionIndex
withRowAnimation:(UITableViewRowAnimation)animation;
- (void)reloadCellViewModelsAtRange:(NSRange)range inSection:(NSInteger)sectionIndex;
- (void)reloadCellViewModelsAtRange:(NSRange)range
inSection:(NSInteger)sectionIndex
withRowAnimation:(UITableViewRowAnimation)animation;
- (void)removeLastCellViewModel;
- (void)removeLastCellViewModelWithRowAnimation:(UITableViewRowAnimation)animation;
- (void)removeLastCellViewModelFromSection:(NSUInteger)sectionIndex;
- (void)removeLastCellViewModelFromSection:(NSUInteger)sectionIndex withRowAnimation:(UITableViewRowAnimation)animation;
- (void)removeCellViewModelAtIndex:(NSUInteger)index;
- (void)removeCellViewModelAtIndex:(NSUInteger)index withRowAnimation:(UITableViewRowAnimation)animation;
- (void)removeCellViewModelAtIndex:(NSUInteger)index fromSection:(NSUInteger)sectionIndex;
- (void)removeCellViewModelAtIndex:(NSUInteger)index
fromSection:(NSUInteger)sectionIndex
withRowAnimation:(UITableViewRowAnimation)animation;
- (void)replaceCellViewModels:(NSArray<__kindof id<LPDTableCellViewModelProtocol>> *)cellViewModels
fromIndex:(NSUInteger)index;
- (void)replaceCellViewModels:(NSArray<__kindof id<LPDTableCellViewModelProtocol>> *)cellViewModels
fromIndex:(NSUInteger)index
withRowAnimation:(UITableViewRowAnimation)animation;
- (void)replaceCellViewModels:(NSArray<__kindof id<LPDTableCellViewModelProtocol>> *)cellViewModels
fromIndex:(NSUInteger)index
inSection:(NSUInteger)sectionIndex;
- (void)replaceCellViewModels:(NSArray<__kindof id<LPDTableCellViewModelProtocol>> *)cellViewModels
fromIndex:(NSUInteger)index
inSection:(NSUInteger)sectionIndex
withRowAnimation:(UITableViewRowAnimation)animation;
- (void)addSectionViewModel:(id<LPDTableSectionViewModelProtocol>)sectionViewModel;
- (void)addSectionViewModel:(id<LPDTableSectionViewModelProtocol>)sectionViewModel
withRowAnimation:(UITableViewRowAnimation)animation;
- (void)addSectionViewModel:(id<LPDTableSectionViewModelProtocol>)sectionViewModel
withCellViewModels:(NSArray<__kindof id<LPDTableCellViewModelProtocol>> *)cellViewModels;
- (void)addSectionViewModel:(id<LPDTableSectionViewModelProtocol>)sectionViewModel
withCellViewModels:(NSArray<__kindof id<LPDTableCellViewModelProtocol>> *)cellViewModels
withRowAnimation:(UITableViewRowAnimation)animation;
- (void)insertSectionViewModel:(id<LPDTableSectionViewModelProtocol>)sectionViewModel atIndex:(NSUInteger)index;
- (void)insertSectionViewModel:(id<LPDTableSectionViewModelProtocol>)sectionViewModel
atIndex:(NSUInteger)index
withRowAnimation:(UITableViewRowAnimation)animation;
- (void)insertSectionViewModel:(id<LPDTableSectionViewModelProtocol>)sectionViewModel
withCellViewModels:(NSArray<__kindof id<LPDTableCellViewModelProtocol>> *)cellViewModels
atIndex:(NSUInteger)index;
- (void)insertSectionViewModel:(id<LPDTableSectionViewModelProtocol>)sectionViewModel
withCellViewModels:(NSArray<__kindof id<LPDTableCellViewModelProtocol>> *)cellViewModels
atIndex:(NSUInteger)index
withRowAnimation:(UITableViewRowAnimation)animation;
- (void)reloadSectionAtIndex:(NSUInteger)index;
- (void)reloadSectionAtIndex:(NSUInteger)index withRowAnimation:(UITableViewRowAnimation)animation;
- (void)reloadSectionsAtRange:(NSRange)range;
- (void)reloadSectionsAtRange:(NSRange)range withRowAnimation:(UITableViewRowAnimation)animation;
- (void)removeSectionAtIndex:(NSUInteger)index;
- (void)removeAllSections;
- (void)removeSectionAtIndex:(NSUInteger)index withRowAnimation:(UITableViewRowAnimation)animation;
- (void)removeAllSectionsWithRowAnimation:(UITableViewRowAnimation)animation;
- (void)replaceSectionWithCellViewModels:(NSArray<__kindof id<LPDTableCellViewModelProtocol>> *)cellViewModels;
- (void)replaceSectionWithCellViewModels:(NSArray<__kindof id<LPDTableCellViewModelProtocol>> *)cellViewModels
withRowAnimation:(UITableViewRowAnimation)animation;
- (void)replaceSectionWithCellViewModels:(NSArray<__kindof id<LPDTableCellViewModelProtocol>> *)cellViewModels
atSection:(NSUInteger)sectionIndex;
- (void)replaceSectionWithCellViewModels:(NSArray<__kindof id<LPDTableCellViewModelProtocol>> *)cellViewModels
atSection:(NSUInteger)sectionIndex
withRowAnimation:(UITableViewRowAnimation)animation;
引入ReactiveCocoa中的RACSignal,将UITableViewDelegate中的委托函数都转换为信号,当我们需要实现某一个委托函数时,只需要订阅对应的RACSignal即可,不订阅没有任何副作用。
@property (nonatomic, strong, readonly) RACSignal *willDisplayCellSignal;
@property (nonatomic, strong, readonly) RACSignal *willDisplayHeaderViewSignal;
@property (nonatomic, strong, readonly) RACSignal *willDisplayFooterViewSignal;
@property (nonatomic, strong, readonly) RACSignal *didEndDisplayingCellSignal;
@property (nonatomic, strong, readonly) RACSignal *didEndDisplayingHeaderViewSignal;
@property (nonatomic, strong, readonly) RACSignal *didEndDisplayingFooterViewSignal;
@property (nonatomic, strong, readonly) RACSignal *didHighlightRowAtIndexPathSignal;
@property (nonatomic, strong, readonly) RACSignal *didUnhighlightRowAtIndexPathSignal;
@property (nonatomic, strong, readonly) RACSignal *didSelectRowAtIndexPathSignal;
@property (nonatomic, strong, readonly) RACSignal *didDeselectRowAtIndexPathSignal;
@property (nonatomic, strong, readonly) RACSignal *willBeginEditingRowAtIndexPathSignal;
@property (nonatomic, strong, readonly) RACSignal *didEndEditingRowAtIndexPathSignal;
Cell、页眉、页脚与其ViewModel之间需要遵守约定好的命名规则,如此会自动匹配。另外,Cell、页眉、页脚默认都是可重用的,同一类型的reuseIdentifier一样,重用相关的函数都在LPDTableViewFactory中。这个类中,当我们关心DataSource或Delegate时,我们只需要与对应的ViewModel交互即可,将Cell、页眉、页脚解耦。
这个protocol的实现类LPDTableSectionViewModel,只是在ViewModel层进行了抽象,这样有利于完善ViewModel层的实现,并不存在对应的SectionView。
cell,header,footer的viewModel中都有对应的height字段,需要根据viewModel的model字段在bindingTo:viewModel函数中设置height值,可以对model进行height的缓存。
-(void)reloadTable {
if (self.datas && self.datas.count > 0) {
NSMutableArray *cellViewModels = [NSMutableArray array];
for (LPDPostModel *model in self.datas) {
LPDTablePostCellViewModel *cellViewModel = [[LPDTablePostCellViewModel alloc]initWithViewModel:self.tableViewModel];
cellViewModel.model = model;
[cellViewModels addObject:cellViewModel];
}
[self.tableViewModel replaceSectionWithCellViewModels:cellViewModels withRowAnimation:UITableViewRowAnimationTop];
}else{
[self.tableViewModel removeAllSections];
}
}
LPDPostModel *model = [[LPDPostModel alloc]init];
model.userId = 111111;
model.identifier = 1003131;
model.title = @"First Chapter";
model.body = @"GitBook allows you to organize your book into chapters, each chapter is stored in a separate file like this one.";
LPDTablePostCellViewModel *cellViewModel = [[LPDTablePostCellViewModel alloc]initWithViewModel:self.tableViewModel];
cellViewModel.model = model;
[self.tableViewModel insertCellViewModel:cellViewModel atIndex:0 withRowAnimation:UITableViewRowAnimationLeft];
NSMutableArray *cellViewModels = [NSMutableArray array];
LPDTableDefaultCellViewModel *cellViewModel1 =
[[LPDTableDefaultCellViewModel alloc] initWithViewModel:self.tableViewModel];
cellViewModel1.text = @"芬兰无法";
cellViewModel1.detail = @"蜂王浆发了";
cellViewModel1.image = [UIImage imageNamed:@"01"];
[cellViewModels addObject:cellViewModel1];
LPDTableValue1CellViewModel *cellViewModel2 =
[[LPDTableValue1CellViewModel alloc] initWithViewModel:self.tableViewModel];
cellViewModel2.text = @"芬兰无法";
cellViewModel2.detail = @"蜂王浆发了";
cellViewModel2.image = [UIImage imageNamed:@"02"];
[cellViewModels addObject:cellViewModel2];
LPDTableValue2CellViewModel *cellViewModel3 =
[[LPDTableValue2CellViewModel alloc] initWithViewModel:self.tableViewModel];
cellViewModel3.text = @"芬兰无法";
cellViewModel3.detail = @"蜂王浆发了";
[cellViewModels addObject:cellViewModel3];
LPDTableSubtitleCellViewModel *cellViewModel4 =
[[LPDTableSubtitleCellViewModel alloc] initWithViewModel:self.tableViewModel];
cellViewModel4.text = @"芬兰无法";
cellViewModel4.detail = @"蜂王浆发了";
cellViewModel4.image = [UIImage imageNamed:@"03"];
[cellViewModels addObject:cellViewModel4];
[self.tableViewModel insertCellViewModels:cellViewModels atIndex:0 withRowAnimation:UITableViewRowAnimationLeft];
[self.tableViewModel removeCellViewModelAtIndex:0 withRowAnimation:UITableViewRowAnimationRight];
[[[self.waybillsTableViewModel.didSelectRowAtIndexPathSignal deliverOnMainThread]
takeUntil:[self rac_willDeallocSignal]] subscribeNext:^(RACTuple *tuple) {
@strongify(self);
__kindof id<LPDTableCellViewModelProtocol> cellViewModel = tuple.second;
LPDWaybillModel *waybillModel = cellViewModel.model;
if (waybillModel.cancelCode == 0) {
LPDWaybillDetailViewModel *detailViewModel = [[LPDWaybillDetailViewModel alloc] init];
detailViewModel.waybillId = waybillModel.waybillId;
[self.navigation pushViewModel:detailViewModel animated:YES];
}
}];
具体请下载lpd-tableview-kit,看看其中的demo。