LPDMvvmKit 系列之 UITableView 的改造

2016-09-27 | 稻子 | iOS

概述

閱讀本文須要對ReactiveCocoa足夠了解,也能夠參閱圖解ReactiveCocoa基本函數git

Cocoa Touch Framework無疑是一個很好的框架,特別是對動畫的支持,在我接觸過的框架中多是最好的(固然我接觸的框架可能比較少),可是就UITableView來講確實存在不少吐槽點,從我我的理解的角度作些分析,嘗試去解決這些吐槽點,並給到的解決方案。github

UITableView枚舉濫用

枚舉歷來都是爲了可擴展而存在的,UITableView中對UITableViewStyle的使用堪稱濫用,先看看這個枚舉的定義,枚舉項的命名不夠直觀,源碼的註釋也得不到有效信息,objective-c

typedef NS_ENUM(NSInteger, UITableViewStyle) {
    UITableViewStylePlain,          // regular table view
    UITableViewStyleGrouped         // preferences style table view
};
複製代碼

再看看以下文檔的說明,基本明確了設計者的本意,UITableViewStyle想要區分的是頁眉或頁腳(section headers or footers)是否浮動,接下來作個剖析:api

case plain
A plain table view. Any section headers or footers are displayed as inline separators and float when the table view is scrolled.
case grouped
A table view whose sections present distinct groups of rows. The section headers and footers do not float.
複製代碼

UITableView的初始化函數緩存

- (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style; // must specify style at creation. -initWithFrame: calls this with UITableViewStylePlain
複製代碼
  • UITableViewStyle做爲初始化函數的參數的不合理性,大多數的UIView及其子類都是同樣風格的初始化函數,到了UITableView這裏就顯得有點另類,設計者將 UITableViewStyle放到初始化函數中做爲參數,無非就是不但願style在UITableView初始化以後被改變,可能緣由是UITableView滑動的過程當中style被改變了,無論以前是否存在浮動的頁眉或頁腳,改變以後對UI的呈現多是比較突兀的,另外這種變動可能並無實際意義;
  • UITableViewStyle的存在不合理,當一個枚舉只存在兩個選項時,不少時候會考慮使用BOOL來表示,可讀性也不差,好比這裏用isSectionGrouped,可能時不須要看注視或者文檔就能夠理解了;
  • UITableViewStylePlain的命名不合理,咱們知道UITableView老是會分section, Plain從其語義和StoryBoard默認值的顯示能夠聯想UITableViewStylePlain多是想表示只有一個section的狀況,那麼所謂的頁眉或頁腳是否浮動其實就沒有太大意義,若是頁眉或頁腳不須要浮動其實就是一個Cell了,由於最終效果都是同樣的,反過來假設須要多個section,可是頁眉或頁腳都不須要浮動,那麼這些頁眉或頁腳其實用Cell來表示是否是更好呢!

綜上得出結論:UITableViewStyle是不應用。bash

UITableViewCell枚舉亂用

UITableViewCell存在好幾個枚舉的亂用,亂用表示不應用的時候用了。app

UITableViewCellStyle的亂用

typedef NS_ENUM(NSInteger, UITableViewCellStyle) {
    UITableViewCellStyleDefault,	// Simple cell with text label and optional image view (behavior of UITableViewCell in iPhoneOS 2.x)
    UITableViewCellStyleValue1,		// Left aligned label on left and right aligned label on right with blue text (Used in Settings)
    UITableViewCellStyleValue2,		// Right aligned label on left with blue text and left aligned label on right (Used in Phone/Contacts)
    UITableViewCellStyleSubtitle	// Left aligned label on top and left aligned label on bottom with gray text (Used in iPod).
};
複製代碼

UITableViewCell的初始化方法中一樣也帶上了UITableViewCellStyle,先看代碼框架

// Designated initializer.  If the cell can be reused, you must pass in a reuse identifier.  You should use the same reuse identifier for all cells of the same form.  
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(nullable NSString *)reuseIdentifier;
複製代碼

若是說UITableView設計者以爲就只存在兩種style,那麼UITableViewCell設計中加入UITableViewCellStyle就顯得徹底是亂用了。同樣的道理,枚舉歷來就不是爲了擴展而存在,UITableViewCell作爲cell的基類,擴展是必須的,不可能全部的cell都長的跟UITableViewCellStyle中定義的幾個枚舉項所分類的徹底同樣,因此這個設計是有多噁心啊。ide

再看看UITableViewCellStyle的各個枚舉項的命名,簡直是殘暴啊,UITableViewCellStyleValue1,UITableViewCellStyleValue2這些是什麼鬼哦,再看看註釋,分別說明Used in Settings和Used in Phone/Contacts,這就很明顯了,這些實現徹底就是系統組件用到了這樣的實現,而後直接作爲api開放出來的,並無作很好的抽象,在初始化函數中加入UITableViewCellStyle,污染了初始化函數,限制了擴展,往往在寫一個UITableViewCell的子類時,老是有一種莫名的哀傷,UITableViewCellStyle作爲參數存在惟一的做用就是多寫了點代碼,而後沒有任何意義。這些cell style所表示的cell徹底應該經過子類化來實現的,因此UITableViewCellStyle的亂用是有點慘不忍睹的。函數

UITableViewCellSeparatorStyle的亂用

typedef NS_ENUM(NSInteger, UITableViewCellSeparatorStyle) {
    UITableViewCellSeparatorStyleNone,
    UITableViewCellSeparatorStyleSingleLine,
    UITableViewCellSeparatorStyleSingleLineEtched   // This separator style is only supported for grouped style table views currently
};
複製代碼

怎麼說也不該該存在這樣一個枚舉,CellSeparatorStyle這裏針對不一樣的UITableViewStyle而設計的,不論是何種style,應該只須要isShowCellSeparatorLine這樣一個BOOL值表示是否須要顯示邊框,若是是UITableViewStyleGrouped這種style,可能須要額外的一個isCellSeparatorLineEtched,若是根據前面的假設,頁眉或頁腳都是默認浮動的話,這樣設計是很合理的。

當一個枚舉各項的命名過於詭異時,這個枚舉的存在其實是要好好考慮下的,因此UITableViewCellSeparatorStyle也是典型的亂用。

UITableViewCell對如下枚舉的使用也是有待商榷的

typedef NS_ENUM(NSInteger, UITableViewCellSelectionStyle) {
    UITableViewCellSelectionStyleNone,
    UITableViewCellSelectionStyleBlue,
    UITableViewCellSelectionStyleGray,
    UITableViewCellSelectionStyleDefault NS_ENUM_AVAILABLE_IOS(7_0)
};
複製代碼

UITableViewCellSelectionStyle想表示cell選中的樣式,這裏大概是經過這種方式來提升幾種默認值,由於CellSelectionStyle仍是能夠定製的,可是UITableViewCellSelectionStyleDefault放在最後UITableViewCellSelectionStyleNone放在最開始,到底誰是default哦;

typedef NS_ENUM(NSInteger, UITableViewCellFocusStyle) {
    UITableViewCellFocusStyleDefault,
    UITableViewCellFocusStyleCustom
} NS_ENUM_AVAILABLE_IOS(9_0);
複製代碼

UITableViewCellFocusStyle這個枚舉的存在難道僅僅是爲了無病呻吟嗎?

UITableView委託亂用

UITableViewDelegate,UITableViewDataSource,包括剛引入的UITableViewDataSourcePrefetching,這幾個delegate的設計好像是缺乏了些設計,更像是爲了解決問題而寫代碼,做爲一個基礎框架,實在是不可取的。

UITableViewDelegate設計之重

// Display customization

- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath;
- (void)tableView:(UITableView *)tableView willDisplayHeaderView:(UIView *)view forSection:(NSInteger)section NS_AVAILABLE_IOS(6_0);
- (void)tableView:(UITableView *)tableView willDisplayFooterView:(UIView *)view forSection:(NSInteger)section NS_AVAILABLE_IOS(6_0);
- (void)tableView:(UITableView *)tableView didEndDisplayingCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath*)indexPath NS_AVAILABLE_IOS(6_0);
- (void)tableView:(UITableView *)tableView didEndDisplayingHeaderView:(UIView *)view forSection:(NSInteger)section NS_AVAILABLE_IOS(6_0);
- (void)tableView:(UITableView *)tableView didEndDisplayingFooterView:(UIView *)view forSection:(NSInteger)section NS_AVAILABLE_IOS(6_0);
複製代碼

這幾個委託函數,都是與Cell、頁眉、頁腳相關的,可是全都集中在UITableViewDelegate這個委託中,且命名都是相似,當一個protocol在定義時存在過多的@optional委託函數時,這個protocol的設計自己就是不合理的,應該拆分紅更細的protocol,咱們應該時在必要的時候選擇相應的protocol,而不是實現存在的@optional委託函數,而後UITableViewDelegate這個protocol自己全部的委託函數都是@optional,這是真的不合理,若是是咱們來設計Cell、頁眉、頁腳實際上都是應該UIView,且存在諸多共同點(參考UICollectionView的設計,Cell、頁眉、頁腳就存在一個共同的基類UICollectionReusableView),應該設計一個UIReusableView,(UICollectionReusableView也能夠不須要了)其中存在以下方法,這些方法能夠在子類中重寫

- (void)willAppear;
- (void)didAppear;
- (void)willDisappear;
- (void)didDisappear
複製代碼

且應該設計一個UIReusableViewDelegate,其包括以下委託函數

- (void)willAppear:(UIReusableView*)reusableView;
- (void)didAppear:(UIReusableView*)reusableView;
- (void)willDisappear:(UIReusableView*)reusableView;
- (void)didDisappear:(UIReusableView*)reusableView;
複製代碼

UIReusableView存在UIReusableViewDelegate的一個delegate,前面所提到的那六個委託函數,實際上應該在Cell、頁眉、頁腳各自須要的時候實現UIReusableViewDelegate。

綜上,UITableViewDelegate其實是過重了。

UITableViewDelegate職責之亂

下面這些委託函數,實際上應該存在UITableViewDataSource中。頁眉、頁腳的數據源跟cell的數據源應該是平等的存在,不該該是說不經常使用了,我就放到UITableViewDelegate中,原本就應該放在UITableViewDataSource,沒必要須用的能夠optional修飾下也還說得過去。

// Variable height support

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath;
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section;
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section;

// Use the estimatedHeight methods to quickly calcuate guessed values which will allow for fast load times of the table.
// If these methods are implemented, the above -tableView:heightForXXX calls will be deferred until views are ready to be displayed, so more expensive logic can be placed there.
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath NS_AVAILABLE_IOS(7_0);
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForHeaderInSection:(NSInteger)section NS_AVAILABLE_IOS(7_0);
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForFooterInSection:(NSInteger)section NS_AVAILABLE_IOS(7_0);

// Section header & footer information. Views are preferred over title should you decide to provide both

- (nullable UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section;   // custom view for header. will be adjusted to default or specified header height
- (nullable UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section;   // custom view for footer. will be adjusted to default or specified footer height
複製代碼

UITableViewDataSource設計之重

通過前面的梳理,那麼UITableViewDataSource中應該包括如下這些函數

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView;
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath;

- (nullable NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section;
- (nullable UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section;
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForHeaderInSection:(NSInteger)section;
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section;

- (nullable NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section;
- (nullable UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section;
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForFooterInSection:(NSInteger)section;
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section;
……
複製代碼

跟前面提到UITableViewDelegate設計之重一個道理,Cell、頁眉、頁腳的DataSource也是應該分開的,在須要的時候實現對應的DataSource,須要定義額外的一個枚舉UIReusableViewType

typedef NS_ENUM(NSInteger, UIReusableViewType) {
    UIReusableViewTypeNone,
    UIReusableViewTypeHeader,
    UIReusableViewTypeFooter
};
複製代碼

而後對頁眉、頁腳就有UIReusableViewDataSource,其中的委託函數以下:

- (nullable NSString *)reusableView:(UIReusableView*)reusableView reusableViewType:(UIReusableViewType)reusableViewType titleInSection:(NSInteger)section;
- (nullable UIView *)reusableView:(UIReusableView*)reusableViewreusableViewType:(UIReusableViewType)reusableViewType viewInSection:(NSInteger)section;
- (CGFloat)reusableView:(UIReusableView*)reusableView reusableViewType:(UIReusableViewType)reusableViewType estimatedHeightInSection:(NSInteger)section;
複製代碼

單獨的針對cell,有UITableViewCellDataSource,其中的委託函數以下:

- (NSInteger)numberOfSections;
- (NSInteger)numberOfRowsInSection:(NSInteger)section;
- (UITableViewCell *)cellForRowAtIndexPath:(NSIndexPath *)indexPath;
- (CGFloat)heightForRowAtIndexPath:(NSIndexPath *)indexPath;
複製代碼

至於UITableViewDataSourcePrefetching就不該該出現,爲了優化滾動幀率,拆東牆補西牆之舉。從開發者的角度,最簡單的作法就是把整個的數據源給到,剩下的就應該是UITableView自身去實現了,數據都有了,想要什麼預加載都是框架自身的事情了,減小對開發者的依賴,更是減小api的耦合度,對外暴露的接口越多越很差。

改造之路在何方?

前面在吐槽的時候,往往會給出自認爲更合理的設計,然而並無什麼卵用,既有代碼是沒法修改的,那改造之路又在何方呢?不能改變既有代碼,那麼只能是將這麼東西儘量的封裝起來,Objective-C語言還提供了一個蠻有意思的編譯期常量NS_UNAVAILABLE,能夠在編譯期禁用父類的方法,算是不完美中的徹底吧,咱們能夠禁用掉一些不合理的類成員,來達到一個比較好的封裝效果。

UITableView枚舉濫用的解決

UITableView能夠禁用被枚舉污染的初始化函數,重寫默認的initWithFrame初始化函數並默認設style爲UITableViewStyleGrouped,參考類 LPDTableView暫時並無重寫初始化函數,目前認爲無傷大雅。

UITableViewCell枚舉亂用的解決

UITableViewCell沒法禁用被枚舉污染的初始化函數,由於重用時會調用到,參考類 LPDTableViewCell,選擇無視UITableViewCellStyle,並將已存在的幾種cellStyle都擴展成對應的子類,LPDTableViewDefaultCell,LPDTableViewValue1Cell,LPDTableViewValue2Cell,LPDTableViewSubtitleCell命名仍是保留一致,畢竟你們都已經習慣了這種醜。

UITableView委託亂用的解決

既然沒法改造既有的UITableView,能夠從另一個側面來解決。

UITableView如何數據驅動

引入MVVM的思想,爲UITableView添加對應的ViewModel,有了ViewModel,則能夠引入數據驅動的方式,當咱們須要爲Cell、頁眉、頁腳提供DataSource時,只須要調用LPDTableViewModelProtocl中的方法就行了,接口的粒度已經比較細了,但可能不是最合理的組合,相關的函數都在下面:

- (nullable NSIndexPath *)indexPathForCellViewModel:(__kindof id<LPDTableCellViewModelProtocol>)cellViewModel;

- (nullable __kindof id<LPDTableCellViewModelProtocol>)cellViewModelFromIndexPath:(NSIndexPath *)indexPath;

- (NSInteger)sectionIndexForHeaderViewModel:(__kindof id<LPDTableHeaderFooterViewModelProtocol>)headerViewModel;

- (nullable __kindof id<LPDTableHeaderFooterViewModelProtocol>)headerViewModelFromSection:(NSInteger)sectionIndex;

- (NSInteger)sectionIndexForFooterViewModel:(__kindof id<LPDTableHeaderFooterViewModelProtocol>)footerViewModel;

- (nullable __kindof id<LPDTableHeaderFooterViewModelProtocol>)footerViewModelFromSection:(NSInteger)sectionIndex;

- (void)addCellViewModel:(__kindof id<LPDTableCellViewModelProtocol>)cellViewModel;

- (void)addCellViewModel:(__kindof id<LPDTableCellViewModelProtocol>)cellViewModel
        withRowAnimation:(UITableViewRowAnimation)animation;

- (void)addCellViewModel:(__kindof id<LPDTableCellViewModelProtocol>)cellViewModel toSection:(NSUInteger)sectionIndex;

- (void)addCellViewModel:(__kindof id<LPDTableCellViewModelProtocol>)cellViewModel
               toSection:(NSUInteger)sectionIndex
        withRowAnimation:(UITableViewRowAnimation)animation;

- (void)addCellViewModels:(NSArray<__kindof id<LPDTableCellViewModelProtocol>> *)cellViewModels;

- (void)addCellViewModels:(NSArray<__kindof id<LPDTableCellViewModelProtocol>> *)cellViewModels
         withRowAnimation:(UITableViewRowAnimation)animation;

- (void)addCellViewModels:(NSArray<__kindof id<LPDTableCellViewModelProtocol>> *)cellViewModels
                toSection:(NSUInteger)sectionIndex;

- (void)addCellViewModels:(NSArray<__kindof id<LPDTableCellViewModelProtocol>> *)cellViewModels
                toSection:(NSUInteger)sectionIndex
         withRowAnimation:(UITableViewRowAnimation)animation;

- (void)insertCellViewModel:(__kindof id<LPDTableCellViewModelProtocol>)cellViewModel atIndex:(NSUInteger)index;

- (void)insertCellViewModel:(__kindof id<LPDTableCellViewModelProtocol>)cellViewModel
                    atIndex:(NSUInteger)index
           withRowAnimation:(UITableViewRowAnimation)animation;

- (void)insertCellViewModel:(__kindof id<LPDTableCellViewModelProtocol>)cellViewModel
                    atIndex:(NSUInteger)index
                  inSection:(NSUInteger)sectionIndex;

- (void)insertCellViewModel:(__kindof id<LPDTableCellViewModelProtocol>)cellViewModel
                    atIndex:(NSUInteger)index
                  inSection:(NSUInteger)sectionIndex
           withRowAnimation:(UITableViewRowAnimation)animation;

- (void)insertCellViewModels:(NSArray<__kindof id<LPDTableCellViewModelProtocol>> *)cellViewModels
                     atIndex:(NSUInteger)index;

- (void)insertCellViewModels:(NSArray<__kindof id<LPDTableCellViewModelProtocol>> *)cellViewModels
                     atIndex:(NSUInteger)index
            withRowAnimation:(UITableViewRowAnimation)animation;

- (void)insertCellViewModels:(NSArray<__kindof id<LPDTableCellViewModelProtocol>> *)cellViewModels
                     atIndex:(NSUInteger)index
                   inSection:(NSUInteger)sectionIndex;

- (void)insertCellViewModels:(NSArray<__kindof id<LPDTableCellViewModelProtocol>> *)cellViewModels
                     atIndex:(NSUInteger)index
               withAnimation:(UITableViewRowAnimation)animation;

- (void)insertCellViewModels:(NSArray<__kindof id<LPDTableCellViewModelProtocol>> *)cellViewModels
                     atIndex:(NSUInteger)index
                   inSection:(NSUInteger)sectionIndex
            withRowAnimation:(UITableViewRowAnimation)animation;

- (void)reloadCellViewModelAtIndex:(NSUInteger)index inSection:(NSInteger)sectionIndex;

- (void)reloadCellViewModelAtIndex:(NSUInteger)index
                         inSection:(NSInteger)sectionIndex
                  withRowAnimation:(UITableViewRowAnimation)animation;

- (void)reloadCellViewModelsAtRange:(NSRange)range inSection:(NSInteger)sectionIndex;

- (void)reloadCellViewModelsAtRange:(NSRange)range
                          inSection:(NSInteger)sectionIndex
                   withRowAnimation:(UITableViewRowAnimation)animation;

- (void)removeLastCellViewModel;

- (void)removeLastCellViewModelWithRowAnimation:(UITableViewRowAnimation)animation;

- (void)removeLastCellViewModelFromSection:(NSUInteger)sectionIndex;

- (void)removeLastCellViewModelFromSection:(NSUInteger)sectionIndex withRowAnimation:(UITableViewRowAnimation)animation;

- (void)removeCellViewModelAtIndex:(NSUInteger)index;

- (void)removeCellViewModelAtIndex:(NSUInteger)index withRowAnimation:(UITableViewRowAnimation)animation;

- (void)removeCellViewModelAtIndex:(NSUInteger)index fromSection:(NSUInteger)sectionIndex;

- (void)removeCellViewModelAtIndex:(NSUInteger)index
                       fromSection:(NSUInteger)sectionIndex
                  withRowAnimation:(UITableViewRowAnimation)animation;

- (void)replaceCellViewModels:(NSArray<__kindof id<LPDTableCellViewModelProtocol>> *)cellViewModels
                    fromIndex:(NSUInteger)index;

- (void)replaceCellViewModels:(NSArray<__kindof id<LPDTableCellViewModelProtocol>> *)cellViewModels
                    fromIndex:(NSUInteger)index
             withRowAnimation:(UITableViewRowAnimation)animation;

- (void)replaceCellViewModels:(NSArray<__kindof id<LPDTableCellViewModelProtocol>> *)cellViewModels
                    fromIndex:(NSUInteger)index
                    inSection:(NSUInteger)sectionIndex;

- (void)replaceCellViewModels:(NSArray<__kindof id<LPDTableCellViewModelProtocol>> *)cellViewModels
                    fromIndex:(NSUInteger)index
                    inSection:(NSUInteger)sectionIndex
             withRowAnimation:(UITableViewRowAnimation)animation;

- (void)addSectionViewModel:(id<LPDTableSectionViewModelProtocol>)sectionViewModel;

- (void)addSectionViewModel:(id<LPDTableSectionViewModelProtocol>)sectionViewModel
           withRowAnimation:(UITableViewRowAnimation)animation;

- (void)addSectionViewModel:(id<LPDTableSectionViewModelProtocol>)sectionViewModel
         withCellViewModels:(NSArray<__kindof id<LPDTableCellViewModelProtocol>> *)cellViewModels;

- (void)addSectionViewModel:(id<LPDTableSectionViewModelProtocol>)sectionViewModel
         withCellViewModels:(NSArray<__kindof id<LPDTableCellViewModelProtocol>> *)cellViewModels
           withRowAnimation:(UITableViewRowAnimation)animation;

- (void)insertSectionViewModel:(id<LPDTableSectionViewModelProtocol>)sectionViewModel atIndex:(NSUInteger)index;

- (void)insertSectionViewModel:(id<LPDTableSectionViewModelProtocol>)sectionViewModel
                       atIndex:(NSUInteger)index
              withRowAnimation:(UITableViewRowAnimation)animation;

- (void)insertSectionViewModel:(id<LPDTableSectionViewModelProtocol>)sectionViewModel
            withCellViewModels:(NSArray<__kindof id<LPDTableCellViewModelProtocol>> *)cellViewModels
                       atIndex:(NSUInteger)index;

- (void)insertSectionViewModel:(id<LPDTableSectionViewModelProtocol>)sectionViewModel
            withCellViewModels:(NSArray<__kindof id<LPDTableCellViewModelProtocol>> *)cellViewModels
                       atIndex:(NSUInteger)index
              withRowAnimation:(UITableViewRowAnimation)animation;

- (void)reloadSectionAtIndex:(NSUInteger)index;

- (void)reloadSectionAtIndex:(NSUInteger)index withRowAnimation:(UITableViewRowAnimation)animation;

- (void)reloadSectionsAtRange:(NSRange)range;

- (void)reloadSectionsAtRange:(NSRange)range withRowAnimation:(UITableViewRowAnimation)animation;

- (void)removeSectionAtIndex:(NSUInteger)index;

- (void)removeAllSections;

- (void)removeSectionAtIndex:(NSUInteger)index withRowAnimation:(UITableViewRowAnimation)animation;

- (void)removeAllSectionsWithRowAnimation:(UITableViewRowAnimation)animation;

- (void)replaceSectionWithCellViewModels:(NSArray<__kindof id<LPDTableCellViewModelProtocol>> *)cellViewModels;

- (void)replaceSectionWithCellViewModels:(NSArray<__kindof id<LPDTableCellViewModelProtocol>> *)cellViewModels
                        withRowAnimation:(UITableViewRowAnimation)animation;

- (void)replaceSectionWithCellViewModels:(NSArray<__kindof id<LPDTableCellViewModelProtocol>> *)cellViewModels
                               atSection:(NSUInteger)sectionIndex;

- (void)replaceSectionWithCellViewModels:(NSArray<__kindof id<LPDTableCellViewModelProtocol>> *)cellViewModels
                               atSection:(NSUInteger)sectionIndex
                        withRowAnimation:(UITableViewRowAnimation)animation;
複製代碼

UITableView委託轉RACSignal

引入ReactiveCocoa中的RACSignal,將UITableViewDelegate中的委函數都轉成信號,當咱們須要實現某一個委託函數,只須要訂閱對應的RACSignal便可,不訂閱沒有任何反作用。

@property (nonatomic, strong, readonly) RACSignal *willDisplayCellSignal;
@property (nonatomic, strong, readonly) RACSignal *willDisplayHeaderViewSignal;
@property (nonatomic, strong, readonly) RACSignal *willDisplayFooterViewSignal;
@property (nonatomic, strong, readonly) RACSignal *didEndDisplayingCellSignal;
@property (nonatomic, strong, readonly) RACSignal *didEndDisplayingHeaderViewSignal;
@property (nonatomic, strong, readonly) RACSignal *didEndDisplayingFooterViewSignal;
@property (nonatomic, strong, readonly) RACSignal *didHighlightRowAtIndexPathSignal;
@property (nonatomic, strong, readonly) RACSignal *didUnhighlightRowAtIndexPathSignal;
@property (nonatomic, strong, readonly) RACSignal *didSelectRowAtIndexPathSignal;
@property (nonatomic, strong, readonly) RACSignal *didDeselectRowAtIndexPathSignal;
@property (nonatomic, strong, readonly) RACSignal *willBeginEditingRowAtIndexPathSignal;
@property (nonatomic, strong, readonly) RACSignal *didEndEditingRowAtIndexPathSignal;
複製代碼

Cell、頁眉、頁腳也存在相應的ViewModel

Cell、頁眉、頁腳跟其ViewModel之間須要遵照約定好的命名規則,如此會自動匹配。另外Cell、頁眉、頁腳默認都是重用的,同一類型reuseIdentifier同樣,重用相關的函數就都在 LPDTableViewFactory。這個類中了當咱們關心DataSource或者Delegate時,咱們只須要跟對應的ViewModel交互便可,將Cell、頁眉、頁腳解耦合。

LPDTableSectionViewModelProtocol

這個protocol的實現類LPDTableSectionViewModel,只是在ViewModel層抽象出來,這樣纔好完善ViewModel層的實現,並不存在對應的SectionView。

關於height

cell,header,footer的viewmodel中都有對應的height字段,須要根據viewmodel的model字段在bindingTo:viewModel函數中設置height值,能夠針對model作height的緩存。

改造以後的例子

加載tableview的數據

-(void)reloadTable {
  if (self.datas && self.datas.count > 0) {
    NSMutableArray *cellViewModels = [NSMutableArray array];
    for (LPDPostModel *model in self.datas) {
      LPDTablePostCellViewModel *cellViewModel = [[LPDTablePostCellViewModel alloc]initWithViewModel:self.tableViewModel];
      cellViewModel.model = model;
      [cellViewModels addObject:cellViewModel];
    }
    [self.tableViewModel replaceSectionWithCellViewModels:cellViewModels withRowAnimation:UITableViewRowAnimationTop];
  }else{
    [self.tableViewModel removeAllSections];
  }
}
複製代碼

添加一個cell

LPDPostModel *model = [[LPDPostModel alloc]init];
        model.userId = 111111;
        model.identifier = 1003131;
        model.title = @"First Chapter";
        model.body = @"GitBook allows you to organize your book into chapters, each chapter is stored in a separate file like this one.";
        LPDTablePostCellViewModel *cellViewModel = [[LPDTablePostCellViewModel alloc]initWithViewModel:self.tableViewModel];
        cellViewModel.model = model;
        [self.tableViewModel insertCellViewModel:cellViewModel atIndex:0 withRowAnimation:UITableViewRowAnimationLeft];
複製代碼

批量添加cell

NSMutableArray *cellViewModels = [NSMutableArray array];
        LPDTableDefaultCellViewModel *cellViewModel1 =
          [[LPDTableDefaultCellViewModel alloc] initWithViewModel:self.tableViewModel];
        cellViewModel1.text = @"芬蘭沒法";
        cellViewModel1.detail = @"蜂王漿發了";
        cellViewModel1.image = [UIImage imageNamed:@"01"];
        [cellViewModels addObject:cellViewModel1];
        LPDTableValue1CellViewModel *cellViewModel2 =
          [[LPDTableValue1CellViewModel alloc] initWithViewModel:self.tableViewModel];
        cellViewModel2.text = @"芬蘭沒法";
        cellViewModel2.detail = @"蜂王漿發了";
        cellViewModel2.image = [UIImage imageNamed:@"02"];
        [cellViewModels addObject:cellViewModel2];
        LPDTableValue2CellViewModel *cellViewModel3 =
          [[LPDTableValue2CellViewModel alloc] initWithViewModel:self.tableViewModel];
        cellViewModel3.text = @"芬蘭沒法";
        cellViewModel3.detail = @"蜂王漿發了";
        [cellViewModels addObject:cellViewModel3];
        LPDTableSubtitleCellViewModel *cellViewModel4 =
          [[LPDTableSubtitleCellViewModel alloc] initWithViewModel:self.tableViewModel];
        cellViewModel4.text = @"芬蘭沒法";
        cellViewModel4.detail = @"蜂王漿發了";
        cellViewModel4.image = [UIImage imageNamed:@"03"];
        [cellViewModels addObject:cellViewModel4];

        [self.tableViewModel insertCellViewModels:cellViewModels atIndex:0 withRowAnimation:UITableViewRowAnimationLeft];
複製代碼

刪除一個cell

[self.tableViewModel removeCellViewModelAtIndex:0 withRowAnimation:UITableViewRowAnimationRight];
複製代碼

Cell的didSelectRowAtIndexPathSignal

[[[self.waybillsTableViewModel.didSelectRowAtIndexPathSignal deliverOnMainThread]
        takeUntil:[self rac_willDeallocSignal]] subscribeNext:^(RACTuple *tuple) {
        @strongify(self);
        __kindof id<LPDTableCellViewModelProtocol> cellViewModel = tuple.second;
        LPDWaybillModel *waybillModel = cellViewModel.model;
        if (waybillModel.cancelCode == 0) {
          LPDWaybillDetailViewModel *detailViewModel = [[LPDWaybillDetailViewModel alloc] init];
          detailViewModel.waybillId = waybillModel.waybillId;
          [self.navigation pushViewModel:detailViewModel animated:YES];
        }
      }];
複製代碼

具體請下載lpd-tableview-kit,看看其中的demo。

若有任何知識產權、版權問題或理論錯誤,還請指正。

轉載請註明原做者及以上信息。

相關文章
相關標籤/搜索