2017.4.23:新增支持数据源彻底依赖网络请求的状况。 ** 2017.4.22:新增支持请求新数据后刷新表格。** ** 2017.4.21: 新增CocoaPods支持:pod 'SJStaticTableView', '~> 1.2.0'。**git
写一个小小轮子~程序员
写UITableView的时候,咱们常常遇到的是彻底依赖于网络请求,须要自定义的动态cell的需求(好比微博帖子列表)。可是同时,大多数app里面几乎也都有设置页,我的页等其余以静态表格为主的页面。github
并且这些页面的共性比较多:编程
由于本身很是想写一个开源的东西出来(也能够暴露本身的不足),同时又受限于水平,因此就打算写这么一个比较简单,又具备通用性的框架:一个定制性比较高的适合在我的页和设置页使用的UITableView。数组
在真正写以前,看了几篇相似的文章,挑出三篇本身以为比较好的:缓存
看完总结以后,利用上周3天的业余时间写好了这个框架,为了它实用性,我仿照了微信客户端的发现页,我的页和设置页写了一个Demo,来看一下效果图:安全
项目所用资源来自:GitHub:zhengwenming/WeChat Demo地址:GitHub: knightsj/SJStaticTableView微信
为了体现出这个框架的定制性,我本身也在里面添加了两个页面,入口在设置页里面:网络
先不要纠结分组定制和同组定制的具体意思,在后面讲到定制性的时候我会详细说明。如今只是让你们看一下效果。架构
在大概了解了功能以后,开始详细介绍这个框架。写这篇介绍的缘由倒不是但愿有多少人来用,而是表达一下我本身的思路而已。各位以为很差的地方请多批评。
在正式讲解以前,先介绍一下本篇的基本目录:
框架总体来讲仍是比较简单的,主要仍是基于苹果的UITableView
组件,为了解耦和责任分离,主要运用了如下技术点:
ViewModel
,让其持有每一个cell的数据(行高,cell类型,文本宽度,图片高度等等)。并且每个section也对应一个ViewModel
,它持有当前section的配置数据(title,header和footer的高度等等)。UITableViewDataSource
与UIViewController
,让单独一个类来实现UITableViewDataSource
的职能。知道了主要运用的技术点之后,给你们详细介绍一下该框架的功能。
这个框架能够用来快速搭建设置页,我的信息页能静态表格页面,使用者只须要给tableView的DataSource传入元素是viewModel的数组就能够了。
虽然说这类页面的布局仍是比较单一的,可是仍是会有几种不一样的状况(cell的布局类型),我对比较常见的cell布局作了封装,使用者能够直接使用。
我在定义这些cell的类型的时候,大体划分了两类:
基于这两大类,再细分了几种状况,能够由下面这张图来直观看一下:
既然是cell的类型,那么就类型的枚举就须要定义在cell的viewModel里面:
typedef NS_ENUM(NSInteger, SJStaticCellType) {
//系统风格的各类cell类型,已封装好,能够直接用
SJStaticCellTypeSystemLogout, //退出登陆cell
SJStaticCellTypeSystemAccessoryNone, //右侧没有任何控件
SJStaticCellTypeSystemAccessorySwitch, //右侧是开关
SJStaticCellTypeSystemAccessoryDisclosureIndicator, //右侧是三角箭头(箭头左侧能够有一个image或者一个label,或者两者都有,根据传入的参数决定)
//须要用户本身添加的自定义cell类型
SJStaticCellTypeMeAvatar, //我的页“我”cell
};
复制代码
来一张图直观得体会一下:
在这里有三点须要说一下:
ViewModel
里面传入相应的类型和数据(文字,图片)便可。在了解了该框架的功能以后,咱们先看一下如何使用这个框架:
pod 'SJStaticTableView', '~> 1.1.2
。具体的方法先用文字说明一下:
SJStaticTableViewController
。createDataSource
方法,将viewModel数组传给控制器的dataSource
属性。didSelectViewModel
方法。可能感受比较抽象,我拿设置页来具体说明一下:
先看一下设置页的布局:
而后咱们看一下设置的ViewController的代码:
- (void)viewDidLoad {
[super viewDidLoad];
self.navigationItem.title = @"设置";
}
- (void)createDataSource
{
self.dataSource = [[SJStaticTableViewDataSource alloc] initWithViewModelsArray:[Factory settingPageData] configureBlock:^(SJStaticTableViewCell *cell, SJStaticTableviewCellViewModel *viewModel) {
switch (viewModel.staticCellType)
{
case SJStaticCellTypeSystemAccessoryDisclosureIndicator:
{
[cell configureAccessoryDisclosureIndicatorCellWithViewModel:viewModel];
}
break;
case SJStaticCellTypeSystemAccessorySwitch:
{
[cell configureAccessorySwitchCellWithViewModel:viewModel];
}
break;
case SJStaticCellTypeSystemLogout:
{
[cell configureLogoutTableViewCellWithViewModel:viewModel];
}
break;
case SJStaticCellTypeSystemAccessoryNone:
{
[cell configureAccessoryNoneCellWithViewModel:viewModel];
}
break;
default:
break;
}
}];
}
- (void)didSelectViewModel:(SJStaticTableviewCellViewModel *)viewModel atIndexPath:(NSIndexPath *)indexPath
{
switch (viewModel.identifier)
{
case 6:
{
NSLog(@"退出登陆");
[self showAlertWithMessage:@"真的要退出登陆嘛?"];
}
break;
case 8:
{
NSLog(@"清理缓存");
}
break;
case 9:
{
NSLog(@"跳转到定制性cell展现页面 - 分组");
SJCustomCellsViewController *vc = [[SJCustomCellsViewController alloc] init];
[self.navigationController pushViewController:vc animated:YES];
}
break;
case 10:
{
NSLog(@"跳转到定制性cell展现页面 - 同组");
SJCustomCellsOneSectionViewController *vc = [[SJCustomCellsOneSectionViewController alloc] init];
[self.navigationController pushViewController:vc animated:YES];
}
break;
default:
break;
}
}
复制代码
看到这里,你可能会有这些疑问:
下面我会一一解答,看完了下面的解答,就能几乎彻底掌握这个框架的思路了:
我本身封装了一个类SJStaticTableViewDataSource
专门做为数据源,须要控制器给它一个viewModel数组。
来看一下它的实现文件:
//SJStaticTableViewDataSource.m
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return self.viewModelsArray.count;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
SJStaticTableviewSectionViewModel *vm = self.viewModelsArray[section];
return vm.cellViewModelsArray.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
//获取section的ViewModel
SJStaticTableviewSectionViewModel *sectionViewModel = self.viewModelsArray[indexPath.section];
//获取cell的viewModel
SJStaticTableviewCellViewModel *cellViewModel = sectionViewModel.cellViewModelsArray[indexPath.row];
SJStaticTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellViewModel.cellID];
if (!cell) {
cell = [[SJStaticTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellViewModel.cellID];
}
self.cellConfigureBlock(cell,cellViewModel);
return cell;
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section{
SJStaticTableviewSectionViewModel *vm = self.viewModelsArray[section];
return vm.sectionHeaderTitle;
}
- (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section
{
SJStaticTableviewSectionViewModel *vm = self.viewModelsArray[section];
return vm.sectionFooterTitle;
}
复制代码
表格的cell和section都设置了与其对应的viewModel,用于封装其对应的数据:
cell的viewModel(大体看一下便可,后面有详细说明):
typedef NS_ENUM(NSInteger, SJStaticCellType) {
//系统风格的各类cell类型,已封装好,能够直接用
SJStaticCellTypeSystemLogout, //退出登陆cell(已封装好)
SJStaticCellTypeSystemAccessoryNone, //右侧没有任何控件
SJStaticCellTypeSystemAccessorySwitch, //右侧是开关
SJStaticCellTypeSystemAccessoryDisclosureIndicator, //右侧是三角箭头(箭头左侧能够有一个image或者一个label,或者两者都有,根据传入的参数决定)
//须要用户本身添加的自定义cell类型
SJStaticCellTypeMeAvatar, //我的页“我”cell
};
typedef void(^SwitchValueChagedBlock)(BOOL isOn); //switch开关切换时调用的block
@interface SJStaticTableviewCellViewModel : NSObject
@property (nonatomic, assign) SJStaticCellType staticCellType; //类型
@property (nonatomic, copy) NSString *cellID; //cell reuser identifier
@property (nonatomic, assign) NSInteger identifier; //区别每一个cell,用于点击
// =============== 系统默认cell左侧 =============== //
@property (nonatomic, strong) UIImage *leftImage; //左侧的image,按需传入
@property (nonatomic, assign) CGSize leftImageSize; //左侧image的大小,存在默认设置
@property (nonatomic, copy) NSString *leftTitle; //cell主标题,按需传入
@property (nonatomic, strong) UIColor *leftLabelTextColor; //当前组cell左侧label里文字的颜色
@property (nonatomic, strong) UIFont *leftLabelTextFont; //当前组cell左侧label里文字的字体
@property (nonatomic, assign) CGFloat leftImageAndLabelGap; //左侧image和label的距离,存在默认值
// =============== 系统默认cell右侧 =============== //
@property (nonatomic, copy) NSString *indicatorLeftTitle; //右侧箭头左侧的文本,按需传入
@property (nonatomic, strong) UIColor *indicatorLeftLabelTextColor; //右侧文字的颜色,存在默认设置,也能够自定义
@property (nonatomic, strong) UIFont *indicatorLeftLabelTextFont; //右侧文字的字体,存在默认设置,也能够自定义
@property (nonatomic, strong) UIImage *indicatorLeftImage; //右侧箭头左侧的image,按需传入
@property (nonatomic, assign) CGSize indicatorLeftImageSize; //右侧尖头左侧image大小,存在默认设置,也能够自定义
@property (nonatomic, assign, readonly) BOOL hasIndicatorImageAndLabel; //右侧尖头左侧的文本和image是否同时存在,只能经过内部计算
@property (nonatomic, assign) CGFloat indicatorLeftImageAndLabelGap; //右侧尖头左侧image和label的距离,存在默认值
@property (nonatomic, assign) BOOL isImageFirst; //右侧尖头左侧的文本和image同时存在时,是不是image挨着箭头,默认为YES
@property (nonatomic, copy) SwitchValueChagedBlock switchValueDidChangeBlock; //切换switch开关的时候调用的block
// =============== 长宽数据 =============== //
@property (nonatomic, assign) CGFloat cellHeight; //cell高度,默认是44,能够设置
@property (nonatomic, assign) CGSize leftTitleLabelSize; //左侧默认Label的size,传入text之后内部计算
@property (nonatomic, assign) CGSize indicatorLeftLabelSize; //右侧label的size
// =============== 自定义cell的数据放在这里 =============== //
@property (nonatomic, strong) UIImage *avatarImage;
@property (nonatomic, strong) UIImage *codeImage;
@property (nonatomic, copy) NSString *userName;
@property (nonatomic, copy) NSString *userID;
复制代码
section的viewModel(大体看一下便可,后面有详细说明):
@interface SJStaticTableviewSectionViewModel : NSObject
@property (nonatomic, copy) NSString *sectionHeaderTitle; //该section的标题
@property (nonatomic, copy) NSString *sectionFooterTitle; //该section的标题
@property (nonatomic, strong) NSArray *cellViewModelsArray; //该section的数据源
@property (nonatomic, assign) CGFloat sectionHeaderHeight; //header的高度
@property (nonatomic, assign) CGFloat sectionFooterHeight; //footer的高度
@property (nonatomic, assign) CGSize leftImageSize; //当前组cell左侧image的大小
@property (nonatomic, strong) UIColor *leftLabelTextColor; //当前组cell左侧label里文字的颜色
@property (nonatomic, strong) UIFont *leftLabelTextFont; //当前组cell左侧label里文字的字体
@property (nonatomic, assign) CGFloat leftImageAndLabelGap; //当前组左侧image和label的距离,存在默认值
@property (nonatomic, strong) UIColor *indicatorLeftLabelTextColor; //当前组cell右侧label里文字的颜色
@property (nonatomic, strong) UIFont *indicatorLeftLabelTextFont; //当前组cell右侧label里文字的字体
@property (nonatomic, assign) CGSize indicatorLeftImageSize; //当前组cell右侧image的大小
@property (nonatomic, assign) CGFloat indicatorLeftImageAndLabelGap;//当前组cell右侧image和label的距离,存在默认值
- (instancetype)initWithCellViewModelsArray:(NSArray *)cellViewModelsArray;
复制代码
你可能会以为属性太多了,但这些属性的存在乎义是为cell的定制性服务的,在后文会有解释。
如今了解了我封装好的数据源,cell的viewModel,section的viewModel之后,咱们看一下第二个问题:
咱们来看一下设置页的viewModel数组的设置:
+ (NSArray *)settingPageData
{
// ========== section 0
SJStaticTableviewCellViewModel *vm0 = [[SJStaticTableviewCellViewModel alloc] init];
vm0.leftTitle = @"帐号与安全";
vm0.identifier = 0;
vm0.indicatorLeftTitle = @"已保护";
vm0.indicatorLeftImage = [UIImage imageNamed:@"ProfileLockOn"];
vm0.isImageFirst = NO;
SJStaticTableviewSectionViewModel *section0 = [[SJStaticTableviewSectionViewModel alloc] initWithCellViewModelsArray:@[vm0]];
// ========== section 1
SJStaticTableviewCellViewModel *vm1 = [[SJStaticTableviewCellViewModel alloc] init];
vm1.leftTitle = @"新消息通知";
vm1.identifier = 1;
//额外添加switch
SJStaticTableviewCellViewModel *vm7 = [[SJStaticTableviewCellViewModel alloc] init];
vm7.leftTitle = @"夜间模式";
vm7.switchValueDidChangeBlock = ^(BOOL isON){
NSString *message = isON?@"打开夜间模式":@"关闭夜间模式";
NSLog(@"%@",message);
};
vm7.staticCellType = SJStaticCellTypeSystemAccessorySwitch;
vm7.identifier = 7;
SJStaticTableviewCellViewModel *vm8 = [[SJStaticTableviewCellViewModel alloc] init];
vm8.leftTitle = @"清理缓存";
vm8.indicatorLeftTitle = @"12.3M";
vm8.identifier = 8;
SJStaticTableviewCellViewModel *vm2 = [[SJStaticTableviewCellViewModel alloc] init];
vm2.leftTitle = @"隐私";
vm2.identifier = 2;
SJStaticTableviewCellViewModel *vm3 = [[SJStaticTableviewCellViewModel alloc] init];
vm3.leftTitle = @"通用";
vm3.identifier = 3;
SJStaticTableviewSectionViewModel *section1 = [[SJStaticTableviewSectionViewModel alloc] initWithCellViewModelsArray:@[vm1,vm7,vm8,vm2,vm3]];
// ========== section 2
SJStaticTableviewCellViewModel *vm4 = [[SJStaticTableviewCellViewModel alloc] init];
vm4.leftTitle = @"帮助与反馈";
vm4.identifier = 4;
SJStaticTableviewCellViewModel *vm5 = [[SJStaticTableviewCellViewModel alloc] init];
vm5.leftTitle = @"关于微信";
vm5.identifier = 5;
SJStaticTableviewSectionViewModel *section2 = [[SJStaticTableviewSectionViewModel alloc] initWithCellViewModelsArray:@[vm4,vm5]];
// ========== section 4
SJStaticTableviewCellViewModel *vm9 = [[SJStaticTableviewCellViewModel alloc] init];
vm9.leftTitle = @"定制性cell展现页面 - 分组";
vm9.identifier = 9;
SJStaticTableviewCellViewModel *vm10 = [[SJStaticTableviewCellViewModel alloc] init];
vm10.leftTitle = @"定制性cell展现页面 - 同组";
vm10.identifier = 10;
SJStaticTableviewSectionViewModel *section4 = [[SJStaticTableviewSectionViewModel alloc] initWithCellViewModelsArray:@[vm9,vm10]];
// ========== section 3
SJStaticTableviewCellViewModel *vm6 = [[SJStaticTableviewCellViewModel alloc] init];
vm6.staticCellType = SJStaticCellTypeSystemLogout;
vm6.cellID = @"logout";
vm6.identifier = 6;
SJStaticTableviewSectionViewModel *section3 = [[SJStaticTableviewSectionViewModel alloc] initWithCellViewModelsArray:@[vm6]];
return @[section0,section1,section2,section4,section3];
}
复制代码
咱们能够看到,交给dataSource的数组是一个二维数组:
SJStaticTableviewSectionViewModel
。SJStaticTableviewCellViewModel
。有几个SJStaticTableviewCellViewModel
的属性须要强调一下:
显然,Factory
类属于Model
,它将“纯数据”交给了dataSource使用的两个viewModel。这个类是我本身定义的,读者在使用这个框架的时候能够根据需求本身定义。
如今知道了数据源的设置方法,咱们看一下第三个问题:
心细的同窗会发现,在dataSource的cellForRow:
方法里,我用了block方法来绘制了cell。
先看一下这个block的定义:
typedef void(^SJStaticCellConfigureBlock)(SJStaticTableViewCell *cell, SJStaticTableviewCellViewModel * viewModel);
复制代码
这个block在控制器里面回调,经过判断cell的类型来绘制不一样的cell。
那么不一样类型的cell是如何区分的呢? --- 我用的是分类。
有分类,就必定有一个被分类的类: SJStaticTableViewCell
看一下它的头文件:
//全部cell都是这个类的分类
@interface SJStaticTableViewCell : UITableViewCell
@property (nonatomic, strong) SJStaticTableviewCellViewModel *viewModel;
// =============== 系统风格cell的全部控件 =============== //
//左半部分
@property (nonatomic, strong) UIImageView *leftImageView; //左侧的ImageView
@property (nonatomic, strong) UILabel *leftTitleLabel; //左侧的Label
//右半部分
@property (nonatomic, strong) UIImageView *indicatorArrow; //右侧的箭头
@property (nonatomic, strong) UIImageView *indicatorLeftImageView; //右侧的箭头的左边的imageview
@property (nonatomic, strong) UILabel *indicatorLeftLabel; //右侧的箭头的左边的Label
@property (nonatomic, strong) UISwitch *indicatorSwitch; //右侧的箭头的左边的开关
@property (nonatomic, strong) UILabel *logoutLabel; //退出登陆的label
// =============== 用户自定义的cell里面的控件 =============== //
//MeViewController里面的头像cell
@property (nonatomic, strong) UIImageView *avatarImageView;
@property (nonatomic, strong) UIImageView *codeImageView;
@property (nonatomic, strong) UIImageView *avatarIndicatorImageView;
@property (nonatomic, strong) UILabel *userNameLabel;
@property (nonatomic, strong) UILabel *userIdLabel;
//统一的,布局cell左侧部分的内容(标题 / 图片 + 标题),全部系统风格的cell都要调用这个方法
- (void)layoutLeftPartSubViewsWithViewModel:(SJStaticTableviewCellViewModel *)viewModel;
@end
复制代码
在这里我定义了全部的控件和一个布局cell左侧的控件的方法。由于几乎全部的分类的左侧几乎都是相似的,因此将它抽取出来。
那么究竟有几个分类呢?(能够参考上面cellViewModel头文件里的枚举类型)
//右侧有剪头的cell(最多见)
@interface SJStaticTableViewCell (AccessoryDisclosureIndicator)
- (void)configureAccessoryDisclosureIndicatorCellWithViewModel:(SJStaticTableviewCellViewModel *)viewModel;
@end
复制代码
//右侧没有控件的cell
@interface SJStaticTableViewCell (AccessoryNone)
- (void)configureAccessoryNoneCellWithViewModel:(SJStaticTableviewCellViewModel *)viewModel;
@end
复制代码
//右侧是开关的 cell
@interface SJStaticTableViewCell (AccessorySwitch)
- (void)configureAccessorySwitchCellWithViewModel:(SJStaticTableviewCellViewModel *)viewModel;
@end
复制代码
//退出登陆cell
@interface SJStaticTableViewCell (Logout)
- (void)configureLogoutTableViewCellWithViewModel:(SJStaticTableviewCellViewModel *)viewModel;
@end
复制代码
//一个自定义的cell(在我的页的第一排)
@interface SJStaticTableViewCell (MeAvatar)
- (void)configureMeAvatarTableViewCellWithViewModel:(SJStaticTableviewCellViewModel *)viewModel;
@end
复制代码
在使用这个框架的时候,若是遇到不知足当前需求的状况,能够本身添加分类。
说到UITableViewDelegate
的代理方法,咱们最熟悉的莫过于didSelectRowAtIndexPath:
了。
可是我在写这个框架的时候,本身定义了一个继承于UITableViewDelegate
的代理:SJStaticTableViewDelegate
,并给它添加了一个代理方法: ``
@protocol SJStaticTableViewDelegate <UITableViewDelegate>
@optional
- (void)didSelectViewModel: (SJStaticTableviewCellViewModel *)viewModel atIndexPath:(NSIndexPath *)indexPath;
@end
复制代码
这个方法返回的是当前点击的cell对应的viewModel,弱化了indexPath的做用。
为何要这么作?
想想原来点击cell的代理方法:didSelectRowAtIndexPath:
。咱们经过这个点击方法,拿到的是cell对应的indexPath,而后再经过这个indexPath,就能够在数据源里面查找对应的模型(viewModel或者model)。
所以,我定义的这个方法直接返回了被点击cell对应的viewModel,等于说帮使用者节省了一个步骤。固然若是要使用的话也可使用系统原来的didSelectRowAtIndexPath:
方法。
来看一下这个新的代理方法是如何实现的:
//SJStaticTableView.m
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView deselectRowAtIndexPath:indexPath animated:YES];
if ((self.sjDelegate) && [self.sjDelegate respondsToSelector:@selector(didSelectViewModel:atIndexPath:)]) {
SJStaticTableviewCellViewModel *cellViewModel = [self.sjDataSource tableView:tableView cellViewModelAtIndexPath:indexPath];
[self.sjDelegate didSelectViewModel:cellViewModel atIndexPath:indexPath];
}else if((self.sjDelegate)&& [self.sjDelegate respondsToSelector:@selector(tableView:didSelectRowAtIndexPath:)]){
[self.sjDelegate tableView:tableView didSelectRowAtIndexPath:indexPath];
}
}
复制代码
如今读者应该大体了解了这个框架的实现思路,如今我讲一下这个框架的定制性。
这个框架有一个配置文件:SJConst.h,它定义了这个框架的全部默认数据和默认配置,好比cell左侧lable的字体,颜色;左侧label和image的距离;右侧label的字体和颜色,右侧图片的默认大小等等。来看一下代码:
#ifndef SJConst_h
#define SJConst_h
//distance
#define SJScreenWidth [UIScreen mainScreen].bounds.size.width
#define SJScreenHeight [UIScreen mainScreen].bounds.size.height
#define SJTopGap 8 //same as bottom gap
#define SJLeftGap 12 //same as right gap
#define SJLeftMiddleGap 10 //in left part: the gap between image and label
#define SJRightMiddleGap 6 //in right part: the gap between image and label
#define SJImgWidth 30 //default width and height
#define SJTitleWidthLimit 180 //limt width of left and right labels
//image
#define SJIndicatorArrow @"arrow"
//font
#define SJLeftTitleTextFont [UIFont systemFontOfSize:15]
#define SJLogoutButtonFont [UIFont systemFontOfSize:16]
#define SJIndicatorLeftTitleTextFont [UIFont systemFontOfSize:13]
//color
#define SJColorWithRGB(R,G,B,A) [UIColor colorWithRed:R/255.0 green:G/255.0 blue:B/255.0 alpha:A]
#define SJLeftTitleTextColor [UIColor blackColor]
#define SJIndicatorLeftTitleTextColor SJColorWithRGB(136,136,136,1)
#endif /* SJConst_h */
复制代码
这里定义的默认配置在cellViewModel和sectionViewModel初始化的时候使用:
cell的viewModel:
//SJStaticTableviewCellViewModel.m
- (instancetype)init
{
self = [super init];
if (self) {
_cellHeight = 44;
_cellID = @"defaultCell";
_staticCellType = SJStaticCellTypeSystemAccessoryDisclosureIndicator;//默认是存在三角箭头的cell
_isImageFirst = YES;
//都是默认配置
_leftLabelTextFont = SJLeftTitleTextFont;
_leftLabelTextColor = SJLeftTitleTextColor;
_leftImageSize = CGSizeMake(SJImgWidth, SJImgWidth);
_leftImageAndLabelGap = SJLeftMiddleGap;
_indicatorLeftLabelTextFont = SJIndicatorLeftTitleTextFont;
_indicatorLeftLabelTextColor = SJIndicatorLeftTitleTextColor;
_indicatorLeftImageSize = CGSizeMake(SJImgWidth, SJImgWidth);
_indicatorLeftImageAndLabelGap = SJRightMiddleGap;
}
return self;
}
复制代码
section的viewModel:
- (instancetype)initWithCellViewModelsArray:(NSArray *)cellViewModelsArray
{
self = [super init];
if (self) {
_sectionHeaderHeight = 10;
_sectionFooterHeight = 10;
_leftLabelTextFont = SJLeftTitleTextFont;
_leftLabelTextColor = SJLeftTitleTextColor;
_leftImageSize = CGSizeMake(SJImgWidth, SJImgWidth);
_leftImageAndLabelGap = SJLeftMiddleGap;
_indicatorLeftLabelTextFont = SJIndicatorLeftTitleTextFont;
_indicatorLeftLabelTextColor = SJIndicatorLeftTitleTextColor;
_indicatorLeftImageSize = CGSizeMake(SJImgWidth, SJImgWidth);
_indicatorLeftImageAndLabelGap = SJRightMiddleGap;
_cellViewModelsArray = cellViewModelsArray;
}
return self;
}
复制代码
显然,这个默认配置只有一组,可是可能一个app里面同时存在一个设置页和一个我的页。而这两个页面的风格也多是不同的,因此这个默认配置只能给其中一个页面,另外一个页面须要另外配置,因而就有了定制性的功能。
再来看一下展现定制性效果的图:
参照这个效果图,咱们看一下这两个页面的数据源是如何设置的:
分组页面:
+ (NSArray *)customCellsPageData
{
//默认配置
SJStaticTableviewCellViewModel *vm1 = [[SJStaticTableviewCellViewModel alloc] init];
vm1.leftImage = [UIImage imageNamed:@"MoreGame"];
vm1.leftTitle = @"所有默认配置,用于对照";
vm1.indicatorLeftImage = [UIImage imageNamed:@"wzry"];
vm1.indicatorLeftTitle = @"王者荣耀!";
SJStaticTableviewSectionViewModel *section1 = [[SJStaticTableviewSectionViewModel alloc] initWithCellViewModelsArray:@[vm1]];
SJStaticTableviewCellViewModel *vm2 = [[SJStaticTableviewCellViewModel alloc] init];
vm2.leftImage = [UIImage imageNamed:@"MoreGame"];
vm2.leftTitle = @"左侧图片变小";
vm2.indicatorLeftImage = [UIImage imageNamed:@"wzry"];
vm2.indicatorLeftTitle = @"王者荣耀!";
SJStaticTableviewSectionViewModel *section2 = [[SJStaticTableviewSectionViewModel alloc] initWithCellViewModelsArray:@[vm2]];
section2.leftImageSize = CGSizeMake(20, 20);
SJStaticTableviewCellViewModel *vm3 = [[SJStaticTableviewCellViewModel alloc] init];
vm3.leftImage = [UIImage imageNamed:@"MoreGame"];
vm3.leftTitle = @"字体变小变红";
vm3.indicatorLeftImage = [UIImage imageNamed:@"wzry"];
vm3.indicatorLeftTitle = @"王者荣耀!";
SJStaticTableviewSectionViewModel *section3 = [[SJStaticTableviewSectionViewModel alloc] initWithCellViewModelsArray:@[vm3]];
section3.leftLabelTextFont = [UIFont systemFontOfSize:8];
section3.leftLabelTextColor = [UIColor redColor];
SJStaticTableviewCellViewModel *vm4 = [[SJStaticTableviewCellViewModel alloc] init];
vm4.leftImage = [UIImage imageNamed:@"MoreGame"];
vm4.leftTitle = @"左侧两个控件距离变大";
vm4.indicatorLeftImage = [UIImage imageNamed:@"wzry"];
vm4.indicatorLeftTitle = @"王者荣耀!";
SJStaticTableviewSectionViewModel *section4 = [[SJStaticTableviewSectionViewModel alloc] initWithCellViewModelsArray:@[vm4]];
section4.leftImageAndLabelGap = 20;
SJStaticTableviewCellViewModel *vm5 = [[SJStaticTableviewCellViewModel alloc] init];
vm5.leftImage = [UIImage imageNamed:@"MoreGame"];
vm5.leftTitle = @"右侧图片变小";
vm5.indicatorLeftImage = [UIImage imageNamed:@"wzry"];
vm5.indicatorLeftTitle = @"王者荣耀!";
SJStaticTableviewSectionViewModel *section5 = [[SJStaticTableviewSectionViewModel alloc] initWithCellViewModelsArray:@[vm5]];
section5.indicatorLeftImageSize = CGSizeMake(15, 15);
SJStaticTableviewCellViewModel *vm6= [[SJStaticTableviewCellViewModel alloc] init];
vm6.leftImage = [UIImage imageNamed:@"MoreGame"];
vm6.leftTitle = @"右侧字体变大变蓝";
vm6.indicatorLeftImage = [UIImage imageNamed:@"wzry"];
vm6.indicatorLeftTitle = @"王者荣耀!";
SJStaticTableviewSectionViewModel *section6 = [[SJStaticTableviewSectionViewModel alloc] initWithCellViewModelsArray:@[vm6]];
section6.indicatorLeftLabelTextFont = [UIFont systemFontOfSize:18];
section6.indicatorLeftLabelTextColor = [UIColor blueColor];
SJStaticTableviewCellViewModel *vm7= [[SJStaticTableviewCellViewModel alloc] init];
vm7.leftImage = [UIImage imageNamed:@"MoreGame"];
vm7.leftTitle = @"右侧两个控件距离变大";
vm7.indicatorLeftImage = [UIImage imageNamed:@"wzry"];
vm7.indicatorLeftTitle = @"王者荣耀!";
SJStaticTableviewSectionViewModel *section7 = [[SJStaticTableviewSectionViewModel alloc] initWithCellViewModelsArray:@[vm7]];
section7.indicatorLeftImageAndLabelGap = 18;
return @[section1,section2,section3,section4,section5,section6,section7];
}
复制代码
咱们能够看到,定制的代码都做用于section的viewModel。
同组页面:
+ (NSArray *)customCellsOneSectionPageData
{
//默认配置
SJStaticTableviewCellViewModel *vm1 = [[SJStaticTableviewCellViewModel alloc] init];
vm1.leftImage = [UIImage imageNamed:@"MoreGame"];
vm1.leftTitle = @"所有默认配置,用于对照";
vm1.indicatorLeftImage = [UIImage imageNamed:@"wzry"];
vm1.indicatorLeftTitle = @"王者荣耀!";
SJStaticTableviewCellViewModel *vm2 = [[SJStaticTableviewCellViewModel alloc] init];
vm2.leftImage = [UIImage imageNamed:@"MoreGame"];
vm2.leftTitle = @"左侧图片变小";
vm2.indicatorLeftImage = [UIImage imageNamed:@"wzry"];
vm2.indicatorLeftTitle = @"王者荣耀!";
vm2.leftImageSize = CGSizeMake(20, 20);
SJStaticTableviewCellViewModel *vm3 = [[SJStaticTableviewCellViewModel alloc] init];
vm3.leftImage = [UIImage imageNamed:@"MoreGame"];
vm3.leftTitle = @"字体变小变红";
vm3.indicatorLeftImage = [UIImage imageNamed:@"wzry"];
vm3.indicatorLeftTitle = @"王者荣耀!";
vm3.leftLabelTextFont = [UIFont systemFontOfSize:8];
vm3.leftLabelTextColor = [UIColor redColor];
SJStaticTableviewCellViewModel *vm4 = [[SJStaticTableviewCellViewModel alloc] init];
vm4.leftImage = [UIImage imageNamed:@"MoreGame"];
vm4.leftTitle = @"左侧两个控件距离变大";
vm4.indicatorLeftImage = [UIImage imageNamed:@"wzry"];
vm4.indicatorLeftTitle = @"王者荣耀!";
vm4.leftImageAndLabelGap = 20;
SJStaticTableviewCellViewModel *vm5 = [[SJStaticTableviewCellViewModel alloc] init];
vm5.leftImage = [UIImage imageNamed:@"MoreGame"];
vm5.leftTitle = @"右侧图片变小";
vm5.indicatorLeftImage = [UIImage imageNamed:@"wzry"];
vm5.indicatorLeftTitle = @"王者荣耀!";
vm5.indicatorLeftImageSize = CGSizeMake(15, 15);
SJStaticTableviewCellViewModel *vm6= [[SJStaticTableviewCellViewModel alloc] init];
vm6.leftImage = [UIImage imageNamed:@"MoreGame"];
vm6.leftTitle = @"右侧字体变大变蓝";
vm6.indicatorLeftImage = [UIImage imageNamed:@"wzry"];
vm6.indicatorLeftTitle = @"王者荣耀!";
vm6.indicatorLeftLabelTextFont = [UIFont systemFontOfSize:18];
vm6.indicatorLeftLabelTextColor = [UIColor blueColor];
SJStaticTableviewCellViewModel *vm7= [[SJStaticTableviewCellViewModel alloc] init];
vm7.leftImage = [UIImage imageNamed:@"MoreGame"];
vm7.leftTitle = @"右侧两个控件距离变大";
vm7.indicatorLeftImage = [UIImage imageNamed:@"wzry"];
vm7.indicatorLeftTitle = @"王者荣耀!";
vm7.indicatorLeftImageAndLabelGap = 18;
SJStaticTableviewSectionViewModel *section1 = [[SJStaticTableviewSectionViewModel alloc] initWithCellViewModelsArray:@[vm1,vm2,vm3,vm4,vm5,vm6,vm7]];
return @[section1];
}
复制代码
为了方便比较,同组页面的定制和分组是一致的。咱们能够看到,定制代码都做用于cell的viewModel上了。
为何要有同组和分组展现?
同组和分组展现的目的,是为了展现这个框架的两种定制性。
分组页面所展现的是section级的定制性:cell的配置任务交给section层的viewModel。一旦设置,该section里面的全部cell都能保持这一配置。
同组页面所展现的是cell级的定制性:cell的配置任务交给cell层的viewModel。一旦设置,只有当前cell具备这个配置,不影响其余cell。
其实为了省事,只在section层的viewModel上配置便可(若是给每一个cell都给设置相同的配置太不优雅了),由于从设计角度来看,一个section里面的cell的风格不一致的状况比较少见(我以为不符合设计):好比在一个section里面,不太可能两个cell里面的图片大小是不同的,或者字体大小也不同。
仍是看一下section级的定制代码吧:
//从新设置了该组所有cell里面左侧label的字体
- (void)setLeftLabelTextFont:(UIFont *)leftLabelTextFont
{
if (_leftLabelTextFont != leftLabelTextFont) {
if (![self font1:_leftLabelTextFont hasSameFontSizeOfFont2:leftLabelTextFont]) {
_leftLabelTextFont = leftLabelTextFont;
//若是新的宽度大于原来的宽度,须要从新设置,不然不须要
[_cellViewModelsArray enumerateObjectsUsingBlock:^(SJStaticTableviewCellViewModel * viewModel, NSUInteger idx, BOOL * _Nonnull stop) {
viewModel.leftLabelTextFont = _leftLabelTextFont;
CGSize size = [self sizeForTitle:viewModel.leftTitle withFont:_leftLabelTextFont];
if (size.width > viewModel.leftTitleLabelSize.width) {
viewModel.leftTitleLabelSize = size;
}
}];
}
}
}
//从新设置了该组所有cell里面左侧label的字的颜色
- (void)setLeftLabelTextColor:(UIColor *)leftLabelTextColor
{
if (![self color1:_leftLabelTextColor hasTheSameRGBAOfColor2:leftLabelTextColor]) {
_leftLabelTextColor = leftLabelTextColor;
[_cellViewModelsArray makeObjectsPerformSelector:@selector(setLeftLabelTextColor:) withObject:_leftLabelTextColor];
}
}
//从新设置了该组所有cell里面左侧图片等大小
- (void)setLeftImageSize:(CGSize)leftImageSize
{
SJStaticTableviewCellViewModel *viewMoel = _cellViewModelsArray.firstObject;
CGFloat cellHeight = viewMoel.cellHeight;
if ( (!CGSizeEqualToSize(_leftImageSize, leftImageSize)) && (leftImageSize.height < cellHeight)) {
_leftImageSize = leftImageSize;
[_cellViewModelsArray enumerateObjectsUsingBlock:^(SJStaticTableviewCellViewModel *viewModel, NSUInteger idx, BOOL * _Nonnull stop)
{
viewMoel.leftImageSize = _leftImageSize;
}];
}
}
复制代码
由于每一个section都持有它内部的全部cell的viewModel,因此在set方法里面,若是发现传进来的配置与当前配置不一致,就须要更新全部cell的viewModel对应的属性。
既然section的ViewModel能作这些,为何还要有一个cell层的配置呢?
-- 只是为了提升配置的自由度罢了,万一忽然来个需求须要某个cell很独特呢?(你们应该知道我说的神么意思 ^^)
cell的viewModel属性的set方法的实现和section的一致,这里就不上代码了。
在1.1.2版本支持了:在更新数据源后,刷新数据源。 举个例子:在发现页模拟网络请求,在请求结束后更新某个cell的viewmodel:
//模拟网络请求
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
//请求成功x
NSDictionary *responseDict = @{@"title_info":@"新游戏上架啦",
@"title_icon":@"game_1",
@"game_info":@"一块儿来玩斗地主呀!",
@"game_icon":@"doudizhu"
};
//将要刷新cell的indexPath
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:1 inSection:3];
//获取cell对应的viewModel
SJStaticTableviewCellViewModel *viewModel = [self.dataSource tableView:self.tableView cellViewModelAtIndexPath:indexPath];
if (viewModel) {
//更新viewModel
viewModel.leftTitle = responseDict[@"title_info"];
viewModel.leftImage = [UIImage imageNamed:responseDict[@"title_icon"]];
viewModel.indicatorLeftImage = [UIImage imageNamed:responseDict[@"game_icon"]];
viewModel.indicatorLeftTitle = responseDict[@"game_info"];
//刷新tableview
[self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade];
}
});
复制代码
效果图:
在1.2.0版本支持了:数据源彻底依赖网络请求的状况。
如今的最新版本里,SJStaticViewController在建立的时候分为两种状况:
//SJStaticTableViewController.h
typedef enum : NSUInteger {
SJDefaultDataTypeExist, //在表格生成以前就有数据(1. 彻底不依赖网络请求,有现成的完整数据 2. 先生成默认数据,而后经过网络请求来更新数据并刷新表格)
SJDefaultDataTypeNone, //没法生成默认数据,须要彻底依赖网络请求,在拿到数据后,生成表格
}SJDefaultDataType;
- (instancetype)initWithDefaultDataType:(SJDefaultDataType)defualtDataType;
复制代码
//SJStaticTableViewController.m
- (instancetype)initWithDefaultDataType:(SJDefaultDataType)defualtDataType
{
self = [super init];
if (self) {
self.defualtDataType = defualtDataType;
}
return self;
}
- (instancetype)init
{
self = [self initWithDefaultDataType:SJDefaultDataTypeExist];//默认是SJDefaultDataTypeExist
return self;
}
- (void)viewDidLoad {
[super viewDidLoad];
[self configureNav];
//在可以提供给tableivew所有,或者部分数据源的状况下,能够先构造出tableview;
//不然,须要在网络请求结束后,手动调用configureTableView方法
if (self.defualtDataType == SJDefaultDataTypeExist) {
[self configureTableView];
}
}
//只有在SJDefaultDataTypeExist的时候才会自动调用,不然须要手动调用
- (void)configureTableView
{
[self createDataSource];//生成数据源
[self createTableView];//生成表格
}
复制代码
看一个例子,咱们将表情页设置为SJDefaultDataTypeNone
,那么就意味着咱们须要手动调用configureTableView
方法:
- (void)viewDidLoad {
[super viewDidLoad];
self.navigationItem.title = @"表情";
[self networkRequest];
}
- (void)networkRequest
{
[MBProgressHUD showHUDAddedTo: self.view animated:YES];
//模拟网络请求
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[MBProgressHUD hideHUDForView: self.view animated:YES];
self.modelsArray = [Factory emoticonPage];//网络请求后,将数据保存在self.modelsArray里面
[self configureTableView];//手动调用
});
}
- (void)createDataSource
{
self.dataSource = [[SJStaticTableViewDataSource alloc] initWithViewModelsArray:self.modelsArray configureBlock:^(SJStaticTableViewCell *cell, SJStaticTableviewCellViewModel *viewModel) {
switch (viewModel.staticCellType) {
case SJStaticCellTypeSystemAccessoryDisclosureIndicator:
{
[cell configureAccessoryDisclosureIndicatorCellWithViewModel:viewModel];
}
break;
default:
break;
}
}];
}
复制代码
看一下效果图:
但愿若是各位以为哪里很差,能够给出您的宝贵意见~
本篇已同步到我的博客:传送门
---------------------------- 2018年7月17日更新 ----------------------------
注意注意!!!
笔者在近期开通了我的公众号,主要分享编程,读书笔记,思考类的文章。
由于公众号天天发布的消息数有限制,因此到目前为止尚未将全部过去的精选文章都发布在公众号上,后续会逐步发布的。
并且由于各大博客平台的各类限制,后面还会在公众号上发布一些短小精干,以小见大的干货文章哦~
扫下方的公众号二维码并点击关注,期待与您的共同成长~