基于MVVM,用于快速搭建設(shè)置頁障本,個(gè)人信息頁的框架


簡書博客已經(jīng)暫停更新,想看更多技術(shù)博客請(qǐng)到:

  • 掘金 :J_Knight_
  • 個(gè)人博客: J_Knight_
  • 個(gè)人公眾號(hào):程序員維他命

更新記錄:

2017.4.23:新增支持?jǐn)?shù)據(jù)源完全依賴網(wǎng)絡(luò)請(qǐng)求的情況响鹃。
** 2017.4.22:新增支持請(qǐng)求新數(shù)據(jù)后刷新表格驾霜。**
** 2017.4.21: 新增CocoaPods支持:pod 'SJStaticTableView', '~> 1.2.0'。**


寫一個(gè)小小輪子~

寫UITableView的時(shí)候茴迁,我們經(jīng)常遇到的是完全依賴于網(wǎng)絡(luò)請(qǐng)求寄悯,需要自定義的動(dòng)態(tài)cell的需求(比如微博帖子列表)。但是同時(shí)堕义,大多數(shù)app里面幾乎也都有設(shè)置頁,個(gè)人頁等其他以靜態(tài)表格為主的頁面。

而且這些頁面的共性比較多:

  1. 大多數(shù)情況下在進(jìn)入頁面之前就已經(jīng)拿到所有數(shù)據(jù)倦卖。
  2. cell樣式單一洒擦,自定義cell出現(xiàn)的幾率比較小(幾乎都是高度為44的cell)怕膛。
  3. 多數(shù)都分組熟嫩。

因?yàn)樽约悍浅O雽懸粋€(gè)開源的東西出來(也可以暴露自己的不足),同時(shí)又受限于水平褐捻,所以就打算寫這么一個(gè)比較簡單掸茅,又具有通用性的框架:一個(gè)定制性比較高的適合在個(gè)人頁和設(shè)置頁使用的UITableView

在真正寫之前柠逞,看了幾篇類似的文章昧狮,挑出三篇自己覺得比較好的:

  1. Clean Table View Code
  2. 如何寫好一個(gè)UITableView
  3. 利用MVVM設(shè)計(jì)快速開發(fā)個(gè)人中心、設(shè)置等模塊

看完總結(jié)之后板壮,利用上周3天的業(yè)余時(shí)間寫好了這個(gè)框架逗鸣,為了它實(shí)用性,我仿照了微信客戶端的發(fā)現(xiàn)頁绰精,個(gè)人頁和設(shè)置頁寫了一個(gè)Demo撒璧,來看一下效果圖:

發(fā)現(xiàn)頁 | 個(gè)人頁 | 個(gè)人信息頁 | 設(shè)置頁

項(xiàng)目所用資源來自:GitHub:zhengwenming/WeChat
Demo地址:GitHub: knightsj/SJStaticTableView

為了體現(xiàn)出這個(gè)框架的定制性,我自己也在里面添加了兩個(gè)頁面笨使,入口在設(shè)置頁里面:

分組定制 | 同組定制

先不要糾結(jié)分組定制和同組定制的具體意思卿樱,在后面講到定制性的時(shí)候我會(huì)詳細(xì)說明。現(xiàn)在只是讓大家看一下效果硫椰。

在大概了解了功能之后殿如,開始詳細(xì)介紹這個(gè)框架。寫這篇介紹的原因倒不是希望有多少人來用最爬,而是表達(dá)一下我自己的思路而已涉馁。各位覺得不好的地方請(qǐng)多批評(píng)。

在正式講解之前爱致,先介紹一下本篇的基本目錄:

  1. 用到的技術(shù)點(diǎn)烤送。
  2. 功能說明。
  3. 使用方法糠悯。
  4. 定制性介紹帮坚。
  5. 新增支持刷新功能。
  6. 新增支持?jǐn)?shù)據(jù)源完全依賴網(wǎng)絡(luò)請(qǐng)求互艾。

1. 用到的技術(shù)點(diǎn)


框架整體來說還是比較簡單的试和,主要還是基于蘋果的UITableView組件,為了解耦和責(zé)任分離纫普,主要運(yùn)用了以下技術(shù)點(diǎn):

  • MVVM:采用MVVM架構(gòu)阅悍,將每一行“純粹”的數(shù)據(jù)交給一個(gè)單獨(dú)的ViewModel,讓其持有每個(gè)cell的數(shù)據(jù)(行高,cell類型,文本寬度节视,圖片高度等等)拳锚。而且每一個(gè)section也對(duì)應(yīng)一個(gè)ViewModel,它持有當(dāng)前section的配置數(shù)據(jù)(title寻行,header和footer的高度等等)霍掺。
  • 輕UIViewController:分離UITableViewDataSourceUIViewController,讓單獨(dú)一個(gè)類來實(shí)現(xiàn)UITableViewDataSource的職能拌蜘。
  • block:使用block來調(diào)用cell的繪制方法杆烁。
  • 分類:使用分類來定義每一種不同的cell的繪制方法。

知道了主要運(yùn)用的技術(shù)點(diǎn)以后简卧,給大家詳細(xì)介紹一下該框架的功能兔魂。

2. 功能介紹


這個(gè)框架可以用來快速搭建設(shè)置頁,個(gè)人信息頁能靜態(tài)表格頁面贞滨,使用者只需要給tableView的DataSource傳入元素是viewModel的數(shù)組就可以了入热。

雖說這類頁面的布局還是比較單一的,但是還是會(huì)有幾種不同的情況(cell的布局類型)晓铆,我對(duì)比較常見的cell布局做了封裝勺良,使用者可以直接使用。

我在定義這些cell的類型的時(shí)候骄噪,大致劃分了兩類:

  1. 第一類是系統(tǒng)風(fēng)格的cell尚困,大多數(shù)情況下,cell高度為44链蕊;在cell左側(cè)會(huì)有一張圖事甜,一個(gè)label,也可以只存在一種(但是只存在圖片的情況很少)滔韵;在cell右側(cè)一般都有一個(gè)向右的箭頭逻谦,而且有時(shí)這個(gè)箭頭的左側(cè)還可能有l(wèi)abel,image陪蜻,也可以兩個(gè)都有邦马。
  2. 第二類就是自定義的cell了,它的高度不一定是44宴卖,而且布局和系統(tǒng)風(fēng)格的cell很不一樣滋将,需要用戶自己添加。

基于這兩大類症昏,再細(xì)分了幾種情況随闽,可以由下面這張圖來直觀看一下:

既然是cell的類型,那么就類型的枚舉就需要定義在cell的viewModel里面:

typedef NS_ENUM(NSInteger, SJStaticCellType) {
    
    //系統(tǒng)風(fēng)格的各種cell類型肝谭,已封裝好掘宪,可以直接用
    SJStaticCellTypeSystemLogout,                          //退出登錄cell
    SJStaticCellTypeSystemAccessoryNone,                   //右側(cè)沒有任何控件
    SJStaticCellTypeSystemAccessorySwitch,                 //右側(cè)是開關(guān)
    SJStaticCellTypeSystemAccessoryDisclosureIndicator,    //右側(cè)是三角箭頭(箭頭左側(cè)可以有一個(gè)image或者一個(gè)label蛾扇,或者二者都有,根據(jù)傳入的參數(shù)決定)
    
    //需要用戶自己添加的自定義cell類型
    SJStaticCellTypeMeAvatar,                              //個(gè)人頁“我”cell    
};

來一張圖直觀得體會(huì)一下:

支持cell類型

在這里有三點(diǎn)需要說一下:

  1. 這里面除了自定義的cell以外添诉,其他類型的cell都不需要開發(fā)者自己布局屁桑,都已經(jīng)被我封裝好医寿,只需要在cell的ViewModel里面?zhèn)魅胂鄳?yīng)的類型和數(shù)據(jù)(文字栏赴,圖片)即可。
  2. 因?yàn)樽髠?cè)的兩個(gè)控件(圖片和文字)是至少存在一個(gè)而且左右順序固定(圖片永遠(yuǎn)在最左側(cè))靖秩,所以該框架通過開發(fā)者傳入的左側(cè)需要顯示的圖片和文字须眷,可以自己進(jìn)行cell的布局。所以類型的判斷主要作用于cell的右側(cè)沟突。
  3. 值得一提的是花颗,在"最右側(cè)是一個(gè)箭頭"子分支的五個(gè)類型其實(shí)都屬于一個(gè)類型,只需要傳入文字和圖片惠拭,以及文字圖片的顯示順序參數(shù)(這個(gè)參數(shù)只在同時(shí)存在圖片和文字的時(shí)候有效)就可以自行判斷布局。

在了解了該框架的功能之后棒呛,我們先看一下如何使用這個(gè)框架:

3. 使用方法


集成方法:

  1. 靜態(tài):手動(dòng)將SJStaticTableViewComponent文件夾拖入到工程中簇秒。
  2. 動(dòng)態(tài):CocoaPods:pod 'SJStaticTableView', '~> 1.1.2秀鞭。

具體的方法先用文字說明一下:

  1. 將要開發(fā)的頁面的ViewController繼承SJStaticTableViewController锋边。
  2. 在新ViewController里實(shí)現(xiàn)createDataSource方法豆巨,將viewModel數(shù)組傳給控制器的dataSource屬性剩辟。
  3. 根據(jù)不同的cell類型,調(diào)用不同的cell繪制方法搀矫。
  4. 如果需要接受cell的點(diǎn)擊抹沪,需要實(shí)現(xiàn)didSelectViewModel方法。

可能感覺比較抽象瓤球,我拿設(shè)置頁來具體說明一下:

先看一下設(shè)置頁的布局:

設(shè)置頁

然后我們看一下設(shè)置的ViewController的代碼:

- (void)viewDidLoad {
    [super viewDidLoad];
    self.navigationItem.title = @"設(shè)置";
}


- (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(@"跳轉(zhuǎn)到定制性cell展示頁面 - 分組");
            SJCustomCellsViewController *vc = [[SJCustomCellsViewController alloc] init];
            [self.navigationController pushViewController:vc animated:YES];
        }
            break;
            
        case 10:
        {
            NSLog(@"跳轉(zhuǎn)到定制性cell展示頁面 - 同組");
            SJCustomCellsOneSectionViewController *vc = [[SJCustomCellsOneSectionViewController alloc] init];
            [self.navigationController pushViewController:vc animated:YES];
        }
            break;
            
        default:
            break;
    }
}

看到這里,你可能會(huì)有這些疑問:

  1. UITableViewDataSource方法哪兒去了卦羡?
  2. viewModel數(shù)組是如何設(shè)置的噪馏?
  3. cell的繪制方法是如何區(qū)分的麦到?
  4. UITableViewDelegate的方法哪里去了?

下面我會(huì)一一解答欠肾,看完了下面的解答瓶颠,就能幾乎完全掌握這個(gè)框架的思路了:

問題1:UITableViewDataSource方法哪兒去了?

我自己封裝了一個(gè)類SJStaticTableViewDataSource專門作為數(shù)據(jù)源刺桃,需要控制器給它一個(gè)viewModel數(shù)組粹淋。

來看一下它的實(shí)現(xiàn)文件:

//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都設(shè)置了與其對(duì)應(yīng)的viewModel,用于封裝其對(duì)應(yīng)的數(shù)據(jù):

cell的viewModel(大致看一下即可桃移,后面有詳細(xì)說明):

typedef NS_ENUM(NSInteger, SJStaticCellType) {
    
    //系統(tǒng)風(fēng)格的各種cell類型借杰,已封裝好,可以直接用
    SJStaticCellTypeSystemLogout,                          //退出登錄cell(已封裝好)
    SJStaticCellTypeSystemAccessoryNone,                   //右側(cè)沒有任何控件
    SJStaticCellTypeSystemAccessorySwitch,                 //右側(cè)是開關(guān)
    SJStaticCellTypeSystemAccessoryDisclosureIndicator,    //右側(cè)是三角箭頭(箭頭左側(cè)可以有一個(gè)image或者一個(gè)label,或者二者都有翩隧,根據(jù)傳入的參數(shù)決定)
    
    //需要用戶自己添加的自定義cell類型
    SJStaticCellTypeMeAvatar,                              //個(gè)人頁“我”cell
    
};


typedef void(^SwitchValueChagedBlock)(BOOL isOn);           //switch開關(guān)切換時(shí)調(diào)用的block


@interface SJStaticTableviewCellViewModel : NSObject

@property (nonatomic, assign) SJStaticCellType staticCellType;                  //類型


@property (nonatomic, copy)   NSString *cellID;                                  //cell reuser identifier
@property (nonatomic, assign) NSInteger identifier;                              //區(qū)別每個(gè)cell,用于點(diǎn)擊

// =============== 系統(tǒng)默認(rèn)cell左側(cè) =============== //
@property (nonatomic, strong) UIImage  *leftImage;                               //左側(cè)的image,按需傳入
@property (nonatomic, assign) CGSize leftImageSize;                              //左側(cè)image的大小蔗怠,存在默認(rèn)設(shè)置

@property (nonatomic, copy)   NSString *leftTitle;                               //cell主標(biāo)題,按需傳入
@property (nonatomic, strong) UIColor *leftLabelTextColor;                       //當(dāng)前組cell左側(cè)label里文字的顏色
@property (nonatomic, strong) UIFont *leftLabelTextFont;                         //當(dāng)前組cell左側(cè)label里文字的字體

@property (nonatomic, assign) CGFloat leftImageAndLabelGap;                      //左側(cè)image和label的距離桥温,存在默認(rèn)值


// =============== 系統(tǒng)默認(rèn)cell右側(cè) =============== //
@property (nonatomic, copy)   NSString *indicatorLeftTitle;                      //右側(cè)箭頭左側(cè)的文本,按需傳入
@property (nonatomic, strong) UIColor *indicatorLeftLabelTextColor;              //右側(cè)文字的顏色区端,存在默認(rèn)設(shè)置,也可以自定義
@property (nonatomic, strong) UIFont *indicatorLeftLabelTextFont;                //右側(cè)文字的字體悔政,存在默認(rèn)設(shè)置槽地,也可以自定義
@property (nonatomic, strong) UIImage *indicatorLeftImage;                       //右側(cè)箭頭左側(cè)的image集畅,按需傳入
@property (nonatomic, assign) CGSize indicatorLeftImageSize;                     //右側(cè)尖頭左側(cè)image大小,存在默認(rèn)設(shè)置赦颇,也可以自定義

@property (nonatomic, assign, readonly)  BOOL hasIndicatorImageAndLabel;         //右側(cè)尖頭左側(cè)的文本和image是否同時(shí)存在,只能通過內(nèi)部計(jì)算

@property (nonatomic, assign) CGFloat indicatorLeftImageAndLabelGap;             //右側(cè)尖頭左側(cè)image和label的距離,存在默認(rèn)值
@property (nonatomic, assign) BOOL isImageFirst;                                 //右側(cè)尖頭左側(cè)的文本和image同時(shí)存在時(shí)鳖敷,是否是image挨著箭頭,默認(rèn)為YES
@property (nonatomic, copy) SwitchValueChagedBlock switchValueDidChangeBlock;    //切換switch開關(guān)的時(shí)候調(diào)用的block


// =============== 長寬數(shù)據(jù) =============== //
@property (nonatomic, assign) CGFloat cellHeight;                                //cell高度,默認(rèn)是44屋吨,可以設(shè)置
@property (nonatomic, assign) CGSize  leftTitleLabelSize;                        //左側(cè)默認(rèn)Label的size鳍徽,傳入text以后內(nèi)部計(jì)算
@property (nonatomic, assign) CGSize  indicatorLeftLabelSize;                    //右側(cè)label的size


// =============== 自定義cell的數(shù)據(jù)放在這里 =============== //
@property (nonatomic, strong) UIImage *avatarImage;
@property (nonatomic, strong) UIImage *codeImage;
@property (nonatomic, copy)   NSString *userName;
@property (nonatomic, copy)   NSString *userID;

section的viewModel(大致看一下即可,后面有詳細(xì)說明):

@interface SJStaticTableviewSectionViewModel : NSObject

@property (nonatomic, copy)   NSString *sectionHeaderTitle;         //該section的標(biāo)題
@property (nonatomic, copy)   NSString *sectionFooterTitle;         //該section的標(biāo)題
@property (nonatomic, strong) NSArray  *cellViewModelsArray;        //該section的數(shù)據(jù)源

@property (nonatomic, assign) CGFloat  sectionHeaderHeight;         //header的高度
@property (nonatomic, assign) CGFloat  sectionFooterHeight;         //footer的高度

@property (nonatomic, assign) CGSize leftImageSize;                 //當(dāng)前組cell左側(cè)image的大小
@property (nonatomic, strong) UIColor *leftLabelTextColor;          //當(dāng)前組cell左側(cè)label里文字的顏色
@property (nonatomic, strong) UIFont *leftLabelTextFont;            //當(dāng)前組cell左側(cè)label里文字的字體
@property (nonatomic, assign) CGFloat leftImageAndLabelGap;         //當(dāng)前組左側(cè)image和label的距離,存在默認(rèn)值

@property (nonatomic, strong) UIColor *indicatorLeftLabelTextColor; //當(dāng)前組cell右側(cè)label里文字的顏色
@property (nonatomic, strong) UIFont *indicatorLeftLabelTextFont;   //當(dāng)前組cell右側(cè)label里文字的字體
@property (nonatomic, assign) CGSize indicatorLeftImageSize;        //當(dāng)前組cell右側(cè)image的大小
@property (nonatomic, assign) CGFloat indicatorLeftImageAndLabelGap;//當(dāng)前組cell右側(cè)image和label的距離瑰剃,存在默認(rèn)值


- (instancetype)initWithCellViewModelsArray:(NSArray *)cellViewModelsArray;

你可能會(huì)覺得屬性太多了,但這些屬性的存在意義是為cell的定制性服務(wù)的挥唠,在后文會(huì)有解釋。

現(xiàn)在了解了我封裝好的數(shù)據(jù)源唤锉,cell的viewModel,section的viewModel以后壁肋,我們看一下第二個(gè)問題:

問題2: viewModel數(shù)組是如何設(shè)置的?

我們來看一下設(shè)置頁的viewModel數(shù)組的設(shè)置:

+ (NSArray *)settingPageData
{
    // ========== section 0
    SJStaticTableviewCellViewModel *vm0 = [[SJStaticTableviewCellViewModel alloc] init];
    vm0.leftTitle = @"賬號(hào)與安全";
    vm0.identifier = 0;
    vm0.indicatorLeftTitle = @"已保護(hù)";
    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?@"打開夜間模式":@"關(guān)閉夜間模式";
        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 = @"關(guān)于微信";
    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的數(shù)組是一個(gè)二維數(shù)組:

  • 第一維是section數(shù)組郑藏,元素是每一個(gè)section對(duì)應(yīng)的viewModel:SJStaticTableviewSectionViewModel
  • 第二維是cell數(shù)組塌忽,元素是每一個(gè)cell對(duì)應(yīng)的viewModel:SJStaticTableviewCellViewModel土居。

有幾個(gè)SJStaticTableviewCellViewModel的屬性需要強(qiáng)調(diào)一下:

  1. isImageFirst:因?yàn)樵擁撁娴谝唤M的cell右側(cè)的箭頭左邊同時(shí)存在一個(gè)image和一個(gè)label甲馋,所以需要額外設(shè)置二者的順序。因?yàn)槟J(rèn)緊挨著箭頭的是圖片痊远,所以我們需要重新設(shè)置它為NO碧聪,作用是讓label緊挨著箭頭。
  2. identifier:這個(gè)屬性是一個(gè)整數(shù)滞造,它用來標(biāo)記每個(gè)cell,用于在用戶點(diǎn)擊cell的時(shí)候進(jìn)行判斷丰泊。我沒有將用戶的點(diǎn)擊與cell的index相關(guān)聯(lián),是因?yàn)橛械臅r(shí)候因?yàn)樾枨笪覀兛赡軙?huì)更改cell的順序或者刪除某個(gè)cell苛败,所以依賴cell的index是不妥的,容易出錯(cuò)缠捌。
  3. cellID:這個(gè)屬性用來cell的復(fù)用柔昼。因?yàn)榭偸怯袀€(gè)別cell的布局是不同的:在這里出現(xiàn)了一個(gè)退出登錄的cell聪姿,所以需要和其他的cell區(qū)別開來(cellID可以不用設(shè)置,有默認(rèn)值,用來標(biāo)記最常用的cell類型)擎场。

顯然,Factory類屬于Model礼饱,它將“純數(shù)據(jù)”交給了dataSource使用的兩個(gè)viewModel。這個(gè)類是我自己定義的够颠,讀者在使用這個(gè)框架的時(shí)候可以根據(jù)需求自己定義剃诅。

現(xiàn)在知道了數(shù)據(jù)源的設(shè)置方法,我們看一下第三個(gè)問題:

問題3:cell的繪制方法是如何區(qū)分的聊品?

心細(xì)的同學(xué)會(huì)發(fā)現(xiàn)擦剑,在dataSource的cellForRow:方法里爬坑,我用了block方法來繪制了cell售担。

先看一下這個(gè)block的定義:

typedef void(^SJStaticCellConfigureBlock)(SJStaticTableViewCell *cell, SJStaticTableviewCellViewModel * viewModel);

這個(gè)block在控制器里面回調(diào)哥攘,通過判斷cell的類型來繪制不同的cell逝淹。

那么不同類型的cell是如何區(qū)分的呢耕姊?
--- 我用的是分類呀袱。

有分類驳概,就一定有一個(gè)被分類的類: SJStaticTableViewCell

看一下它的頭文件:

//所有cell都是這個(gè)類的分類

@interface SJStaticTableViewCell : UITableViewCell

@property (nonatomic, strong) SJStaticTableviewCellViewModel *viewModel;

// =============== 系統(tǒng)風(fēng)格cell的所有控件 =============== //

//左半部分
@property (nonatomic, strong) UIImageView *leftImageView;               //左側(cè)的ImageView
@property (nonatomic, strong) UILabel *leftTitleLabel;                  //左側(cè)的Label

//右半部分
@property (nonatomic, strong) UIImageView *indicatorArrow;              //右側(cè)的箭頭
@property (nonatomic, strong) UIImageView *indicatorLeftImageView;      //右側(cè)的箭頭的左邊的imageview
@property (nonatomic, strong) UILabel *indicatorLeftLabel;              //右側(cè)的箭頭的左邊的Label
@property (nonatomic, strong) UISwitch *indicatorSwitch;                //右側(cè)的箭頭的左邊的開關(guān)
@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;


//統(tǒng)一的抚岗,布局cell左側(cè)部分的內(nèi)容(標(biāo)題 / 圖片 + 標(biāo)題)呻拌,所有系統(tǒng)風(fēng)格的cell都要調(diào)用這個(gè)方法
- (void)layoutLeftPartSubViewsWithViewModel:(SJStaticTableviewCellViewModel *)viewModel;

@end

在這里我定義了所有的控件和一個(gè)布局cell左側(cè)的控件的方法。因?yàn)閹缀跛械姆诸惖淖髠?cè)幾乎都是類似的滥玷,所以將它抽取出來杠袱。

那么究竟有幾個(gè)分類呢塘安?(可以參考上面cellViewModel頭文件里的枚舉類型)

//右側(cè)有剪頭的cell(最常見)
@interface SJStaticTableViewCell (AccessoryDisclosureIndicator)
- (void)configureAccessoryDisclosureIndicatorCellWithViewModel:(SJStaticTableviewCellViewModel *)viewModel;
@end
//右側(cè)沒有控件的cell
@interface SJStaticTableViewCell (AccessoryNone)
- (void)configureAccessoryNoneCellWithViewModel:(SJStaticTableviewCellViewModel *)viewModel;
@end
//右側(cè)是開關(guān)的 cell
@interface SJStaticTableViewCell (AccessorySwitch)
- (void)configureAccessorySwitchCellWithViewModel:(SJStaticTableviewCellViewModel *)viewModel;
@end
//退出登錄cell
@interface SJStaticTableViewCell (Logout)
- (void)configureLogoutTableViewCellWithViewModel:(SJStaticTableviewCellViewModel *)viewModel;
@end
//一個(gè)自定義的cell(在個(gè)人頁的第一排)
@interface SJStaticTableViewCell (MeAvatar)
- (void)configureMeAvatarTableViewCellWithViewModel:(SJStaticTableviewCellViewModel *)viewModel;
@end

在使用這個(gè)框架的時(shí)候栽连,如果遇到不滿足當(dāng)前需求的情況臭笆,可以自己添加分類瓶竭。

問題4:UITableViewDelegate的方法哪里去了?

說到UITableViewDelegate的代理方法淮腾,我們最熟悉的莫過于didSelectRowAtIndexPath:了挑童。

但是我在寫這個(gè)框架的時(shí)候,自己定義了一個(gè)繼承于UITableViewDelegate的代理:SJStaticTableViewDelegate,并給它添加了一個(gè)代理方法:
``

@protocol SJStaticTableViewDelegate <UITableViewDelegate>

@optional

- (void)didSelectViewModel: (SJStaticTableviewCellViewModel *)viewModel atIndexPath:(NSIndexPath *)indexPath;

@end

這個(gè)方法返回的是當(dāng)前點(diǎn)擊的cell對(duì)應(yīng)的viewModel,弱化了indexPath的作用。

為什么要這么做绒尊?

想一想原來點(diǎn)擊cell的代理方法:didSelectRowAtIndexPath:景描。我們通過這個(gè)點(diǎn)擊方法赡突,拿到的是cell對(duì)應(yīng)的indexPath赴穗,然后再通過這個(gè)indexPath弱判,就可以在數(shù)據(jù)源里面查找對(duì)應(yīng)的模型(viewModel或者model)株婴。

因此座哩,我定義的這個(gè)方法直接返回了被點(diǎn)擊cell對(duì)應(yīng)的viewModel递递,等于說幫使用者節(jié)省了一個(gè)步驟。當(dāng)然如果要使用的話也可以使用系統(tǒng)原來的didSelectRowAtIndexPath:方法拳芙。

來看一下這個(gè)新的代理方法是如何實(shí)現(xiàn)的:

//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];
        
    }
}

現(xiàn)在讀者應(yīng)該大致了解了這個(gè)框架的實(shí)現(xiàn)思路察藐,現(xiàn)在我講一下這個(gè)框架的定制性。

4. 定制性


這個(gè)框架有一個(gè)配置文件:SJConst.h舟扎,它定義了這個(gè)框架的所有默認(rèn)數(shù)據(jù)和默認(rèn)配置分飞,比如cell左側(cè)lable的字體,顏色睹限;左側(cè)label和image的距離譬猫;右側(cè)label的字體和顏色,右側(cè)圖片的默認(rèn)大小等等羡疗。來看一下代碼:

#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 */

這里定義的默認(rèn)配置在cellViewModel和sectionViewModel初始化的時(shí)候使用:

cell的viewModel:

//SJStaticTableviewCellViewModel.m
- (instancetype)init
{
    self = [super init];
    if (self) {        
        _cellHeight = 44;
        _cellID = @"defaultCell";
        _staticCellType = SJStaticCellTypeSystemAccessoryDisclosureIndicator;//默認(rèn)是存在三角箭頭的cell
        _isImageFirst = YES;
        
        //都是默認(rèn)配置
        _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;
}

顯然染服,這個(gè)默認(rèn)配置只有一組,但是可能一個(gè)app里面同時(shí)存在一個(gè)設(shè)置頁和一個(gè)個(gè)人頁叨恨。而這兩個(gè)頁面的風(fēng)格也可能是不一樣的柳刮,所以這個(gè)默認(rèn)配置只能給其中一個(gè)頁面,另一個(gè)頁面需要另外配置,于是就有了定制性的功能秉颗。

再來看一下展示定制性效果的圖:

分組定制 | 同組定制

參照這個(gè)效果圖痢毒,我們看一下這兩個(gè)頁面的數(shù)據(jù)源是如何設(shè)置的:

分組頁面:

+ (NSArray *)customCellsPageData
{
    //默認(rèn)配置
    SJStaticTableviewCellViewModel *vm1 = [[SJStaticTableviewCellViewModel alloc] init];
    vm1.leftImage = [UIImage imageNamed:@"MoreGame"];
    vm1.leftTitle = @"全部默認(rèn)配置,用于對(duì)照";
    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 = @"左側(cè)圖片變小";
    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 = @"左側(cè)兩個(gè)控件距離變大";
    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 = @"右側(cè)圖片變小";
    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 = @"右側(cè)字體變大變藍(lán)";
    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 = @"右側(cè)兩個(gè)控件距離變大";
    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
{
    //默認(rèn)配置
    SJStaticTableviewCellViewModel *vm1 = [[SJStaticTableviewCellViewModel alloc] init];
    vm1.leftImage = [UIImage imageNamed:@"MoreGame"];
    vm1.leftTitle = @"全部默認(rèn)配置,用于對(duì)照";
    vm1.indicatorLeftImage = [UIImage imageNamed:@"wzry"];
    vm1.indicatorLeftTitle = @"王者榮耀!";
    
    
    SJStaticTableviewCellViewModel *vm2 = [[SJStaticTableviewCellViewModel alloc] init];
    vm2.leftImage = [UIImage imageNamed:@"MoreGame"];
    vm2.leftTitle = @"左側(cè)圖片變小";
    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 = @"左側(cè)兩個(gè)控件距離變大";
    vm4.indicatorLeftImage = [UIImage imageNamed:@"wzry"];
    vm4.indicatorLeftTitle = @"王者榮耀!";
    vm4.leftImageAndLabelGap = 20;
    
    
    SJStaticTableviewCellViewModel *vm5 = [[SJStaticTableviewCellViewModel alloc] init];
    vm5.leftImage = [UIImage imageNamed:@"MoreGame"];
    vm5.leftTitle = @"右側(cè)圖片變小";
    vm5.indicatorLeftImage = [UIImage imageNamed:@"wzry"];
    vm5.indicatorLeftTitle = @"王者榮耀!";
    vm5.indicatorLeftImageSize = CGSizeMake(15, 15);
    
    
    SJStaticTableviewCellViewModel *vm6= [[SJStaticTableviewCellViewModel alloc] init];
    vm6.leftImage = [UIImage imageNamed:@"MoreGame"];
    vm6.leftTitle = @"右側(cè)字體變大變藍(lán)";
    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 = @"右側(cè)兩個(gè)控件距離變大";
    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上了敏释。

為什么要有同組和分組展示?

同組和分組展示的目的摸袁,是為了展示這個(gè)框架的兩種定制性钥顽。

  • 分組頁面所展示的是section級(jí)的定制性:cell的配置任務(wù)交給section層的viewModel。一旦設(shè)置靠汁,該section里面的所有cell都能保持這一配置蜂大。

  • 同組頁面所展示的是cell級(jí)的定制性:cell的配置任務(wù)交給cell層的viewModel。一旦設(shè)置蝶怔,只有當(dāng)前cell具有這個(gè)配置奶浦,不影響其他cell。

其實(shí)為了省事踢星,只在section層的viewModel上配置即可(如果給每個(gè)cell都給設(shè)置相同的配置太不優(yōu)雅了)澳叉,因?yàn)閺脑O(shè)計(jì)角度來看,一個(gè)section里面的cell的風(fēng)格不一致的情況比較少見(我覺得不符合設(shè)計(jì)):比如在一個(gè)section里面沐悦,不太可能兩個(gè)cell里面的圖片大小是不一樣的成洗,或者字體大小也不一樣。

還是看一下section級(jí)的定制代碼吧:

//重新設(shè)置了該組全部cell里面左側(cè)label的字體
- (void)setLeftLabelTextFont:(UIFont *)leftLabelTextFont
{
    if (_leftLabelTextFont != leftLabelTextFont) {
        
        if (![self font1:_leftLabelTextFont hasSameFontSizeOfFont2:leftLabelTextFont]) {
            
            _leftLabelTextFont = leftLabelTextFont;
            
            //如果新的寬度大于原來的寬度藏否,需要重新設(shè)置瓶殃,否則不需要
            [_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;
                }
            }];
            
        }
    }
}

//重新設(shè)置了該組全部cell里面左側(cè)label的字的顏色
- (void)setLeftLabelTextColor:(UIColor *)leftLabelTextColor
{
    if (![self color1:_leftLabelTextColor hasTheSameRGBAOfColor2:leftLabelTextColor]) {
         _leftLabelTextColor = leftLabelTextColor;
        [_cellViewModelsArray makeObjectsPerformSelector:@selector(setLeftLabelTextColor:) withObject:_leftLabelTextColor];
    }
}

//重新設(shè)置了該組全部cell里面左側(cè)圖片等大小
- (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;
        }];
    }
}

因?yàn)槊總€(gè)section都持有它內(nèi)部的所有cell的viewModel,所以在set方法里面副签,如果發(fā)現(xiàn)傳進(jìn)來的配置與當(dāng)前配置不一致遥椿,就需要更新所有cell的viewModel對(duì)應(yīng)的屬性。

既然section的ViewModel能做這些继薛,為什么還要有一個(gè)cell層的配置呢修壕?

-- 只是為了提高配置的自由度罷了,萬一突然來個(gè)需求需要某個(gè)cell很獨(dú)特呢遏考?(大家應(yīng)該知道我說的神么意思 ^^)

cell的viewModel屬性的set方法的實(shí)現(xiàn)和section的一致慈鸠,這里就不上代碼了。

5. 新增支持刷新功能

在1.1.2版本支持了:在更新數(shù)據(jù)源后,刷新數(shù)據(jù)源青团。
舉個(gè)例子:在發(fā)現(xiàn)頁模擬網(wǎng)絡(luò)請(qǐng)求譬巫,在請(qǐng)求結(jié)束后更新某個(gè)cell的viewmodel:

//模擬網(wǎng)絡(luò)請(qǐng)求
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        
        //請(qǐng)求成功x
        NSDictionary *responseDict = @{@"title_info":@"新游戲上架啦",
                                       @"title_icon":@"game_1",
                                       @"game_info":@"一起來玩斗地主呀!",
                                       @"game_icon":@"doudizhu"
                                       };
        //將要刷新cell的indexPath
        NSIndexPath *indexPath = [NSIndexPath indexPathForRow:1 inSection:3];
        
        //獲取cell對(duì)應(yīng)的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];
        }
 });

效果圖:

更新數(shù)據(jù)源后刷新表格

6. 新增支持?jǐn)?shù)據(jù)源完全依賴網(wǎng)絡(luò)請(qǐng)求

在1.2.0版本支持了:數(shù)據(jù)源完全依賴網(wǎng)絡(luò)請(qǐng)求的情況督笆。

現(xiàn)在的最新版本里芦昔,SJStaticViewController在創(chuàng)建的時(shí)候分為兩種情況:

  1. SJDefaultDataTypeExist:在表格生成之前就存在數(shù)據(jù),可以是表格的全部數(shù)據(jù)娃肿,也可以是表格的默認(rèn)數(shù)據(jù)(后來通過網(wǎng)絡(luò)請(qǐng)求來更新部分?jǐn)?shù)據(jù)咕缎,參考上一節(jié))。
  2. SJDefaultDataTypeNone:意味著當(dāng)前沒有任何的默認(rèn)數(shù)據(jù)可以使用料扰,也就是無法生成tableview凭豪,需要在網(wǎng)絡(luò)請(qǐng)求拿到數(shù)據(jù)后,再手動(dòng)調(diào)用生成數(shù)據(jù)源晒杈,生成表格的方法嫂伞。
//SJStaticTableViewController.h
typedef enum : NSUInteger {
    
    SJDefaultDataTypeExist,    //在表格生成之前就有數(shù)據(jù)(1. 完全不依賴網(wǎng)絡(luò)請(qǐng)求,有現(xiàn)成的完整數(shù)據(jù) 2. 先生成默認(rèn)數(shù)據(jù)拯钻,然后通過網(wǎng)絡(luò)請(qǐng)求來更新數(shù)據(jù)并刷新表格)
    SJDefaultDataTypeNone,     //無法生成默認(rèn)數(shù)據(jù)帖努,需要完全依賴網(wǎng)絡(luò)請(qǐng)求,在拿到數(shù)據(jù)后粪般,生成表格
    
}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];//默認(rèn)是SJDefaultDataTypeExist
    return self;
}

- (void)viewDidLoad {
    
    [super viewDidLoad];
    [self configureNav];
    
    //在能夠提供給tableivew全部拼余,或者部分?jǐn)?shù)據(jù)源的情況下,可以先構(gòu)造出tableview刊驴;
    //否則姿搜,需要在網(wǎng)絡(luò)請(qǐng)求結(jié)束后,手動(dòng)調(diào)用configureTableView方法
    if (self.defualtDataType == SJDefaultDataTypeExist) {
        [self configureTableView];
    }
}

//只有在SJDefaultDataTypeExist的時(shí)候才會(huì)自動(dòng)調(diào)用捆憎,否則需要手動(dòng)調(diào)用
- (void)configureTableView
{
    [self createDataSource];//生成數(shù)據(jù)源
    [self createTableView];//生成表格
}

看一個(gè)例子舅柜,我們將表情頁設(shè)置為SJDefaultDataTypeNone,那么就意味著我們需要手動(dòng)調(diào)用configureTableView方法:

- (void)viewDidLoad {
    
    [super viewDidLoad];
    
     self.navigationItem.title = @"表情";
    [self networkRequest];
}


- (void)networkRequest
{
    [MBProgressHUD showHUDAddedTo: self.view animated:YES];
    
    //模擬網(wǎng)絡(luò)請(qǐng)求
    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];//網(wǎng)絡(luò)請(qǐng)求后躲惰,將數(shù)據(jù)保存在self.modelsArray里面
        [self configureTableView];//手動(dòng)調(diào)用
        
    });
}

- (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;
        }
    }];
}

看一下效果圖:



好了致份,到這里就講差不多了,代碼量雖然不多础拨,但是都說清楚還是感覺挺需要時(shí)間想的氮块。

希望如果各位覺得哪里不好,可以給出您的寶貴意見~

本篇已同步到個(gè)人博客:傳送門


本文已在版權(quán)印備案诡宗,如需轉(zhuǎn)載請(qǐng)?jiān)L問版權(quán)印滔蝉。48422928

獲取授權(quán)

-------------------------------- 2018年7月16日更新 --------------------------------

注意注意!K帧蝠引!

筆者在近期開通了個(gè)人公眾號(hào),主要分享編程,讀書筆記螃概,思考類的文章矫夯。

  • 編程類文章:包括筆者以前發(fā)布的精選技術(shù)文章,以及后續(xù)發(fā)布的技術(shù)文章(以原創(chuàng)為主)吊洼,并且逐漸脫離 iOS 的內(nèi)容训貌,將側(cè)重點(diǎn)會(huì)轉(zhuǎn)移到提高編程能力的方向上。
  • 讀書筆記類文章:分享編程類冒窍,思考類递沪,心理類職場類書籍的讀書筆記综液。
  • 思考類文章:分享筆者平時(shí)在技術(shù)上区拳,生活上的思考。

因?yàn)楣娞?hào)每天發(fā)布的消息數(shù)有限制意乓,所以到目前為止還沒有將所有過去的精選文章都發(fā)布在公眾號(hào)上,后續(xù)會(huì)逐步發(fā)布的约素。

而且因?yàn)楦鞔蟛┛推脚_(tái)的各種限制届良,后面還會(huì)在公眾號(hào)上發(fā)布一些短小精干,以小見大的干貨文章哦~

掃下方的公眾號(hào)二維碼并點(diǎn)擊關(guān)注圣猎,期待與您的共同成長~

公眾號(hào):程序員維他命
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末士葫,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子送悔,更是在濱河造成了極大的恐慌慢显,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,427評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件欠啤,死亡現(xiàn)場離奇詭異荚藻,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)洁段,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門应狱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人祠丝,你說我怎么就攤上這事疾呻。” “怎么了写半?”我有些...
    開封第一講書人閱讀 165,747評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵岸蜗,是天一觀的道長。 經(jīng)常有香客問我叠蝇,道長璃岳,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,939評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮矾睦,結(jié)果婚禮上晦款,老公的妹妹穿的比我還像新娘。我一直安慰自己枚冗,他們只是感情好缓溅,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,955評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著赁温,像睡著了一般坛怪。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上股囊,一...
    開封第一講書人閱讀 51,737評(píng)論 1 305
  • 那天袜匿,我揣著相機(jī)與錄音,去河邊找鬼稚疹。 笑死居灯,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的内狗。 我是一名探鬼主播怪嫌,決...
    沈念sama閱讀 40,448評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼柳沙!你這毒婦竟也來了岩灭?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,352評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤赂鲤,失蹤者是張志新(化名)和其女友劉穎噪径,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體数初,經(jīng)...
    沈念sama閱讀 45,834評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡找爱,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,992評(píng)論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了妙真。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片缴允。...
    茶點(diǎn)故事閱讀 40,133評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖珍德,靈堂內(nèi)的尸體忽然破棺而出练般,到底是詐尸還是另有隱情,我是刑警寧澤锈候,帶...
    沈念sama閱讀 35,815評(píng)論 5 346
  • 正文 年R本政府宣布薄料,位于F島的核電站,受9級(jí)特大地震影響泵琳,放射性物質(zhì)發(fā)生泄漏摄职。R本人自食惡果不足惜誊役,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,477評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望谷市。 院中可真熱鬧蛔垢,春花似錦、人聲如沸迫悠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽创泄。三九已至艺玲,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間鞠抑,已是汗流浹背饭聚。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評(píng)論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留搁拙,地道東北人秒梳。 一個(gè)月前我還...
    沈念sama閱讀 48,398評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像箕速,于是被迫代替她去往敵國和親端幼。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,077評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • 前言 由于最近兩個(gè)多月弧满,筆者正和小伙伴們忙于對(duì)公司新項(xiàng)目的開發(fā),筆者主要負(fù)責(zé)項(xiàng)目整體架構(gòu)的搭建以及功能模塊的分工此熬。...
    CoderMikeHe閱讀 27,032評(píng)論 74 271
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫庭呜、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,107評(píng)論 4 62
  • 周三的時(shí)候犀忱,我結(jié)束第一場關(guān)于老奶奶的庭審募谎。坦率講這是我到目前為止處理的最復(fù)雜的案子。我本來想要寫一篇驚天地泣鬼神的...
    生如如花閱讀 658評(píng)論 0 2
  • 他將全球化分割成三個(gè)階段:第一個(gè)階級(jí)即其所定義的全球化1.0版本阴汇,始于哥倫布發(fā)現(xiàn)美洲新大陸数冬,從1492年持續(xù)到18...
    陳誠chen閱讀 487評(píng)論 0 0
  • 前兩日回了趟老家拐纱,車開到村口時(shí),突然跑出來兩個(gè)小姑娘哥倔,大的約莫十歲小的剛剛會(huì)走秸架,隨后一個(gè)穿白色寬大T恤的女人急忙出...
    留逝時(shí)光閱讀 294評(píng)論 1 4