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日更新 ----------------------------
注意注意!!!
筆者在近期開通了我的公衆號,主要分享編程,讀書筆記,思考類的文章。
由於公衆號天天發佈的消息數有限制,因此到目前爲止尚未將全部過去的精選文章都發布在公衆號上,後續會逐步發佈的。
並且由於各大博客平臺的各類限制,後面還會在公衆號上發佈一些短小精幹,以小見大的乾貨文章哦~
掃下方的公衆號二維碼並點擊關注,期待與您的共同成長~