閱讀本文需要對ReactiveCocoa足夠了解,也可以參閱圖解ReactiveCocoa基本函式
Cocoa Touch Framework無疑是一個很好的框架,特別是對動畫的支援,在我接觸過的框架中可能是最好的(當然我接觸的框架可能比較少),但是就UITableView來說確實存在很多吐槽點,從我個人理解的角度做些分析,嘗試去解決這些吐槽點,並給到的解決方案。
UITableView列舉濫用
列舉從來都是為了可擴充套件而存在的,UITableView中對UITableViewStyle的使用堪稱濫用,先看看這個列舉的定義,列舉項的命名不夠直觀,原始碼的註釋也得不到有效資訊,
1 2 3 4 |
typedef NS_ENUM(NSInteger, UITableViewStyle) { UITableViewStylePlain, // regular table view UITableViewStyleGrouped // preferences style table view }; |
再看看如下文件的說明,基本明確了設計者的本意,UITableViewStyle想要區分的是頁首或頁尾(section headers or footers)是否浮動,接下來做個剖析:
1 2 3 4 |
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的初始化函式
1 |
- (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是不該用。
UITableViewCell列舉亂用
UITableViewCell存在好幾個列舉的亂用,亂用表示不該用的時候用了。
UITableViewCellStyle的亂用
1 2 3 4 5 6 |
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,先看程式碼
1 2 |
// 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中定義的幾個列舉項所分類的完全一樣,所以這個設計是有多噁心啊。
再看看UITableViewCellStyle的各個列舉項的命名,簡直是殘暴啊,UITableViewCellStyleValue1,UITableViewCellStyleValue2這些是什麼鬼哦,再看看註釋,分別說明Used in Settings和Used in Phone/Contacts,這就很明顯了,這些實現完全就是系統元件用到了這樣的實現,然後直接做為api開放出來的,並沒有做很好的抽象,在初始化函式中加入UITableViewCellStyle,汙染了初始化函式,限制了擴充套件,每每在寫一個UITableViewCell的子類時,總是有一種莫名的哀傷,UITableViewCellStyle做為引數存在唯一的作用就是多寫了點程式碼,然後沒有任何意義。這些cell style所表示的cell完全應該通過子類化來實現的,所以UITableViewCellStyle的亂用是有點慘不忍睹的。
UITableViewCellSeparatorStyle的亂用
1 2 3 4 5 |
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對以下列舉的使用也是有待商榷的
1 2 3 4 5 6 |
typedef NS_ENUM(NSInteger, UITableViewCellSelectionStyle) { UITableViewCellSelectionStyleNone, UITableViewCellSelectionStyleBlue, UITableViewCellSelectionStyleGray, UITableViewCellSelectionStyleDefault NS_ENUM_AVAILABLE_IOS(7_0) }; |
UITableViewCellSelectionStyle想表示cell選中的樣式,這裡大概是通過這種方式來提高几種預設值,因為CellSelectionStyle還是可以定製的,但是UITableViewCellSelectionStyleDefault放在最後UITableViewCellSelectionStyleNone放在最開始,到底誰是default哦;
1 2 3 4 |
typedef NS_ENUM(NSInteger, UITableViewCellFocusStyle) { UITableViewCellFocusStyleDefault, UITableViewCellFocusStyleCustom } NS_ENUM_AVAILABLE_IOS(9_0); |
UITableViewCellFocusStyle這個列舉的存在難道僅僅是為了無病呻吟嗎?
UITableView委託亂用
UITableViewDelegate,UITableViewDataSource,包括剛引入的UITableViewDataSourcePrefetching,這幾個delegate的設計好像是缺少了些設計,更像是為了解決問題而寫程式碼,作為一個基礎框架,實在是不可取的。
UITableViewDelegate設計之重
1 2 3 4 5 6 7 8 |
// 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也可以不需要了)其中存在如下方法,這些方法可以在子類中重寫
1 2 3 4 |
- (void)willAppear; - (void)didAppear; - (void)willDisappear; - (void)didDisappear |
且應該設計一個UIReusableViewDelegate,其包括如下委託函式
1 2 3 4 |
- (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修飾下也還說得過去。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
// 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中應該包括以下這些函式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
- (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
1 2 3 4 5 |
typedef NS_ENUM(NSInteger, UIReusableViewType) { UIReusableViewTypeNone, UIReusableViewTypeHeader, UIReusableViewTypeFooter }; |
然後對頁首、頁尾就有UIReusableViewDataSource,其中的委託函式如下:
1 2 3 |
- (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,其中的委託函式如下:
1 2 3 4 |
- (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中的方法就好了,介面的粒度已經比較細了,但可能不是最合理的組合,相關的函式都在下面:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 |
- (nullable NSIndexPath *)indexPathForCellViewModel:(__kindof id)cellViewModel; - (nullable __kindof id)cellViewModelFromIndexPath:(NSIndexPath *)indexPath; - (NSInteger)sectionIndexForHeaderViewModel:(__kindof id)headerViewModel; - (nullable __kindof id)headerViewModelFromSection:(NSInteger)sectionIndex; - (NSInteger)sectionIndexForFooterViewModel:(__kindof id)footerViewModel; - (nullable __kindof id)footerViewModelFromSection:(NSInteger)sectionIndex; - (void)addCellViewModel:(__kindof id)cellViewModel; - (void)addCellViewModel:(__kindof id)cellViewModel withRowAnimation:(UITableViewRowAnimation)animation; - (void)addCellViewModel:(__kindof id)cellViewModel toSection:(NSUInteger)sectionIndex; - (void)addCellViewModel:(__kindof id)cellViewModel toSection:(NSUInteger)sectionIndex withRowAnimation:(UITableViewRowAnimation)animation; - (void)addCellViewModels:(NSArray> *)cellViewModels; - (void)addCellViewModels:(NSArray> *)cellViewModels withRowAnimation:(UITableViewRowAnimation)animation; - (void)addCellViewModels:(NSArray> *)cellViewModels toSection:(NSUInteger)sectionIndex; - (void)addCellViewModels:(NSArray> *)cellViewModels toSection:(NSUInteger)sectionIndex withRowAnimation:(UITableViewRowAnimation)animation; - (void)insertCellViewModel:(__kindof id)cellViewModel atIndex:(NSUInteger)index; - (void)insertCellViewModel:(__kindof id)cellViewModel atIndex:(NSUInteger)index withRowAnimation:(UITableViewRowAnimation)animation; - (void)insertCellViewModel:(__kindof id)cellViewModel atIndex:(NSUInteger)index inSection:(NSUInteger)sectionIndex; - (void)insertCellViewModel:(__kindof id)cellViewModel atIndex:(NSUInteger)index inSection:(NSUInteger)sectionIndex withRowAnimation:(UITableViewRowAnimation)animation; - (void)insertCellViewModels:(NSArray> *)cellViewModels atIndex:(NSUInteger)index; - (void)insertCellViewModels:(NSArray> *)cellViewModels atIndex:(NSUInteger)index withRowAnimation:(UITableViewRowAnimation)animation; - (void)insertCellViewModels:(NSArray> *)cellViewModels atIndex:(NSUInteger)index inSection:(NSUInteger)sectionIndex; - (void)insertCellViewModels:(NSArray> *)cellViewModels atIndex:(NSUInteger)index withAnimation:(UITableViewRowAnimation)animation; - (void)insertCellViewModels:(NSArray> *)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> *)cellViewModels fromIndex:(NSUInteger)index; - (void)replaceCellViewModels:(NSArray> *)cellViewModels fromIndex:(NSUInteger)index withRowAnimation:(UITableViewRowAnimation)animation; - (void)replaceCellViewModels:(NSArray> *)cellViewModels fromIndex:(NSUInteger)index inSection:(NSUInteger)sectionIndex; - (void)replaceCellViewModels:(NSArray> *)cellViewModels fromIndex:(NSUInteger)index inSection:(NSUInteger)sectionIndex withRowAnimation:(UITableViewRowAnimation)animation; - (void)addSectionViewModel:(id)sectionViewModel; - (void)addSectionViewModel:(id)sectionViewModel withRowAnimation:(UITableViewRowAnimation)animation; - (void)addSectionViewModel:(id)sectionViewModel withCellViewModels:(NSArray> *)cellViewModels; - (void)addSectionViewModel:(id)sectionViewModel withCellViewModels:(NSArray> *)cellViewModels withRowAnimation:(UITableViewRowAnimation)animation; - (void)insertSectionViewModel:(id)sectionViewModel atIndex:(NSUInteger)index; - (void)insertSectionViewModel:(id)sectionViewModel atIndex:(NSUInteger)index withRowAnimation:(UITableViewRowAnimation)animation; - (void)insertSectionViewModel:(id)sectionViewModel withCellViewModels:(NSArray> *)cellViewModels atIndex:(NSUInteger)index; - (void)insertSectionViewModel:(id)sectionViewModel withCellViewModels:(NSArray> *)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> *)cellViewModels; - (void)replaceSectionWithCellViewModels:(NSArray> *)cellViewModels withRowAnimation:(UITableViewRowAnimation)animation; - (void)replaceSectionWithCellViewModels:(NSArray> *)cellViewModels atSection:(NSUInteger)sectionIndex; - (void)replaceSectionWithCellViewModels:(NSArray> *)cellViewModels atSection:(NSUInteger)sectionIndex withRowAnimation:(UITableViewRowAnimation)animation; |
UITableView委託轉RACSignal
引入ReactiveCocoa中的RACSignal,將UITableViewDelegate中的委函式都轉成訊號,當我們需要實現某一個委託函式,只需要訂閱對應的RACSignal即可,不訂閱沒有任何副作用。
1 2 3 4 5 6 7 8 9 10 11 12 |
@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的資料
1 2 3 4 5 6 7 8 9 10 11 12 13 |
-(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
1 2 3 4 5 6 7 8 |
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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
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
1 |
[self.tableViewModel removeCellViewModelAtIndex:0 withRowAnimation:UITableViewRowAnimationRight]; |
Cell的didSelectRowAtIndexPathSignal
1 2 3 4 5 6 7 8 9 10 11 |
[[[self.waybillsTableViewModel.didSelectRowAtIndexPathSignal deliverOnMainThread] takeUntil:[self rac_willDeallocSignal]] subscribeNext:^(RACTuple *tuple) { @strongify(self); __kindof id 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。