FFEasyModel-简介
基本介绍
在iOS开发中,我们经常会碰到使用tableview
或者collectionview
来承接复杂页面展示的情况。面对复杂的页面结构和数据结构,通常会在tableview
或者collectionview
的datasource
和delegate
中使用大量重复的if..else if..else
来描述页面逻辑。当碰到需求变化页面改动时,更是痛苦不堪。
FFEasyModel就是用来解决这种问题的。他的目标是简化delegate
和datasource
的逻辑代码,将页面逻辑进行统一的管理,使业务逻辑更加清晰,一目了然。
基本使用方法
在PCH中#import "FFEasyModel.h"
,这将会导入所需要的全部文件,因为我们会在UIView+FFEasyData.h
中为UIView
扩展一些属性和方法,这是使用FFEasyModel
必不可少的.
1.注册需要使用的cell
[self.tableView registerClass:[FFStringTableViewCell class] forCellReuseIdentifier:[FFStringTableViewCell ff_identifier]];
可以看到这里使用到了[FFStringTableViewCell ff_identifier]
,这是在UIView+FFEasyData.h
中已经为我们扩展的方法。
1.创建模型
- (void)updateDataSource
{
//self.data = @[@"基本collection",@"复杂操作collection"];
self.dataSource = [NSMutableArray array];
//创建section模型 用来管理整个section相关的信息
FFEasyTableSectionModel *sectionModel = [[FFEasyTableSectionModel alloc] init];
NSMutableArray *cellModels = [NSMutableArray array];
for (NSInteger i = 0; i < self.data.count; i ++) {
//创建cell模型
//这里将这个cell用到的UI、代理对象、数据对象以及简单点击操作方法传递给cellModel
__weak typeof(self)weakself = self;
FFEasyTableCellModel *cellModel = [[FFEasyTableCellModel alloc] initWithClassName:NSStringFromClass([FFStringTableViewCell class]) delegate:self data:self.data[i] tapBlock:^{
[weakself jumpToPage:i];
}];
[cellModels addObject:cellModel];
}
//把cell模型交给section模型
sectionModel.cellModels = cellModels;
//把section模型交给section模型数组
[self.dataSource addObject:sectionModel];
[self.tableView reloadData];
}
3.tableview的delegate和datasource
#pragma mark - tableview delegate & datasource
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return self.dataSource.count;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.dataSource[section].cellModels.count;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return self.dataSource[indexPath.section].cellModels[indexPath.row].cellHeight;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [self.dataSource[indexPath.section].cellModels[indexPath.row] cellForTableView:tableView];
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView deselectRowAtIndexPath:indexPath animated:YES];
dispatch_block_t block = self.dataSource[indexPath.section].cellModels[indexPath.row].tapBlock;
if (block) {
block();
}
}
可以看到tableview的delegate和datasource代码已经变得非常简洁,并且可以在多个页面直接复制黏贴使用。
4.cell中实现相关函数
@implementation FFStringTableViewCell
- (void)setFf_data:(NSString *)ff_data
{
[super setFf_data:ff_data];
//这里可以做模型赋值,也可以直接调整界面相关元素
self.textLabel.text = ff_data;
}
- (void)setFf_delegate:(id)ff_delegate
{
//这里可以做代理对象的赋值
[super setFf_delegate:ff_delegate];
}
- (void)ff_clear
{
//这里可以做一些cell复用前的清理工作
}
+ (CGFloat)ff_viewHeightWithModel:(id)model
{
//返回cell的高度
return 60.0f;
}
@end
通过以上步骤,整个tableview的展示逻辑都被归在了updateDataSource
这个方法里。其他人接手代码时也能够清楚地明白当前页面的展示逻辑。
基本实现原理
1.UIView+FFEasyData.h
@interface UIView (FFEasyData)
@property (nonatomic, strong) id ff_data;//用来存储视图的数据对象
@property (nonatomic, weak) id ff_delegate;//用来存储视图的操作代理对象
- (void)ff_clear;//cell重用时的清除多余资源占用动作
+ (NSString *)ff_identifier;//cell的重用标记
+ (CGFloat)ff_viewHeightWithModel:(id)model;//用来获取tableview情况下的cell高度
+ (CGSize)ff_viewSizeWithModel:(id)model;//用来获取collectionview情况下的cell大小
@end
这里为UIView扩展了相关属性是后面简化代码的关键。
2.FFEasyTableSectionModel.h
section模型
@interface FFEasyTableSectionModel : NSObject
@property (nonatomic, strong) FFEasyTableHeaderModel *headerModel;//头视图模型
@property (nonatomic, copy) NSArray<FFEasyTableCellModel *> *cellModels;//cell模型数组
@property (nonatomic, strong) FFEasyTableFooterModel *footerModel;//脚视图模型
@end
创建一个section模型来保存一个section需要的基本元素,header、footer和cell模型。这个section模型会在tableview的delegate和datasource中为tableview提供相关对象模型。
3.FFEasyTableCellModel.h
@interface FFEasyTableCellModel : NSObject
@property (nonatomic, strong) NSString *className;//保存了视图类名
@property (nonatomic, weak) id delegate;//代理对象
@property (nonatomic, strong) id data;//数据对象
@property (nonatomic, copy) dispatch_block_t tapBlock;//点击操作
@property (nonatomic, assign) CGFloat height;//高度 TODO:做高度缓存
//这个方法来生成cell模型 传入所需要的相关数据
- (instancetype)initWithClassName:(NSString *)className delegate:(id)delegate data:(id)data tapBlock:(dispatch_block_t)tapBlock;
//返回cell视图实例
- (UITableViewCell *)cellForTableView:(UITableView *)tableView;
//获取cell高度
- (CGFloat)cellHeight;
@end
再看.m中的实现
@implementation FFEasyTableCellModel
- (instancetype)initWithClassName:(NSString *)className delegate:(id)delegate data:(id)data tapBlock:(dispatch_block_t)tapBlock
{
self = [super init];
if (self) {
_className = className;
_delegate = delegate;
_data = data;
_tapBlock = tapBlock;
}
return self;
}
- (UITableViewCell *)cellForTableView:(UITableView *)tableView
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:self.className];
if (!cell) {
cell = [[NSClassFromString(self.className) alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:self.className];
}
cell.ff_delegate = self.delegate;
cell.ff_data = self.data;
return cell;
}
- (CGFloat)cellHeight
{
_height = 0;
return self.height;
}
- (CGFloat)height
{
if (!_height) {
//通过UIView+FFEasyData扩展的ff_viewHeightWithModel向cell获取高度
_height = [NSClassFromString(self.className) ff_viewHeightWithModel:self.data];
}
return _height;
}
@end
这样我们很容易地将原本写在tableview的delegate和datasource中的代码,分别交给了cellModel去实现,赋值设置过程和返回高度交给了cell本身去实现。
更多
原本FFEasyModel只是一个支持tableview的工具,但是在公司业务不断变化中tableview已经无法继续支持复杂的页面逻辑,因此我扩展了对collectionview的相关支持。同时为了保持界面的流畅性和支持对单个section和cell的刷新,我又增加了FFEasyCollectionModel.h
来实现相关插入删除更新动作,避免不断updateDataSource
造成性能消耗和页面闪动,提升用户体验。
未完成的
collection的header、footer也应该返回Size,我偷了个懒。
代理设置貌似有点问题。
。。。
FFEasyModel DEMO 在这里.