如何寫好靜態的TableView

前言

可能以前的表述不是特別明確,個人方案不是靜態頁面的通用實現。個人方案針對的是相似一些設置界面之類的簡單的靜態的tableView。看到有不少人認爲這樣的方案感受實現起來會變麻煩,這個可能就是思考問題的側重點不一樣。我思考的側重點是後期的維護修改、應對頻繁的需求變動 歡迎交流~~ios

靜態的tableView相似設置界面、我的主頁等等幾乎是每一個APP都會涉及到的一個模塊。我相信你們都有一些本身的套路來如何處理這類界面。寫這篇文章的目的是拋磚引玉想要和你們來交流交流。git

一些常見的寫法

從具體的寫法來切入,如下是我能想到的一些寫法。程序員

1. 山頂洞人寫法

啥都不封裝github

if (indexPath.section == 0) {
        if (indexPath.row == 0) {
            
        }else if (indexPath.row == 1) {
            
        }
    }else if (indexPath.section == 1) {
        if (indexPath.row == 0) {
            
        }else if (indexPath.row == 1) {
            
        }
    }
複製代碼

各類嵌套if判斷indexPath.section indexPath.row拿到對應的cell顯示或者跳轉。這種方式可讀性差,很差擴展應該沒人這麼寫了吧,可能你剛學iOS開發的時候這麼寫過。swift

2. 純代碼 + 枚舉

用一條枚舉來對應的一條cell。 數據源用枚舉數組,或者也能夠用帶有枚舉屬性的對象數組。數組

self.dataArray = @[@[@(settingTypeAccount)],
                       @[@(settingTypeMessage),@(settingTypePrivacy)],
                       @[@(settingTypeHelp),@(settingTypeAboutUs)]];
複製代碼

代理方法裏能夠拿到枚舉直接用switch判斷xcode

switch (type) {
        case settingTypeHelp:
            break;
            .
            .
            .
        default:
            break;
    }
複製代碼

用switch來判斷具體具體的cell,首先可讀性相較於if判斷高了很多,而且在增長刪除cell的狀況下,xcode會有提示來幫助不至於漏掉一些地方。這種方式會有比較多的重複代碼,並且在添加、刪除、調整cell的時候不夠高效。安全

3. storyboard的靜態cell

首先storyboard方式相較於純代碼,不用跑起來就能看見界面,相對比較直觀。並且在開發速度方面也有不小的優點。可是我從自身開發過程當中的狀況看來,這種方式在需求頻繁變動的狀況下仍是比較蛋疼的。bash

4. 加一層中間層

沒有什麼封裝是加一層中間層解決不了的,若是有那麼再加一層 -- 魯迅ui

😁開個玩笑,來看看加一層怎麼樣操做。首先我認爲要有一個概念,封裝在必定程度上是不會減小代碼量的。該寫的代碼你仍是要寫的,只是合理的結構可讓代碼可讀性更好,可擴展性也更好。

@interface tableModel : NSObject
- (void)addASection:(tableSectionModel *)section;
@property (nonatomic,strong) NSMutableArray <tableSectionModel *> *sections;
.
.
.
@end

@interface tableSectionModel : NSObject
- (void)addARow:(tableRowModel *)row;
@property (nonatomic,strong) NSMutableArray <tableRowModel *> *rows;
.
.
.
@end

@interface tableRowModel : NSObject
@property (nonatomic,assign) NSInteger rowHeight;
.
.
.
@end
複製代碼

咱們一開始就已近知道了table是如何展現的,包括cell的顯示順序,cell的顯示樣式、行高等等。那麼咱們能夠把可以描述一個cell的全部的數據都抽象成一個rowModel的數據,而後把能描述每一個section的的全部數據抽象成一個sectionModel的數據。那麼咱們只須要生成對應的sectionModel的數組就能夠來描述一個table了,而後咱們在數據源裏解析model裏面的數據完成顯示。這種方式已經相對比較合理了,可是內部仍是有比較大的封裝餘地。

show you my code

我看過挺多的相似三行代碼實現設置界面的方案,基本都是上面第四種方法的進一步封裝,內部實現了幾種常見的cell樣式,用一個枚舉來對應具體的樣式,而後給每一個每一個rowModel添加一個cell樣式的屬性。這樣一來經過簡單的設置rowModel的cell樣式就能拿到具體的cell。固然這樣的方式已經可以應對大部分的狀況,而且寫起界面來也是很爽了。可是我仍是有幾個地方不是太滿意,須要去嘗試解決這些不滿意。

1. 添加section和row的方式

我但願添加section和row的時候這部分的代碼是一個總體,單純- (void)addASection:(tableSectionModel *)section;這樣的寫法在我看來還不夠總體,由於你沒辦法保證這部分的代碼必定是寫在了一個地方。思來想去,我想到了Masonry的寫法。

[self.view mas_makeConstraints:^(MASConstraintMaker *make) {
    // do something        
}];
複製代碼

這種寫法解決了我不滿意的地方,因此最後我但願的寫法是

[self.tableView zhn_addSection:^(ZHNStaticTableSection *section) {
        [section zhn_addRow:^(ZHNStaticTableRow *row) {
            
        }];
        
        .
        .
        .
    }];
複製代碼

2.dataSource delegate 重複代碼的問題

咱們清楚dataSourcedelegate是一對一的,因此代理方法和數據源方法確定是只能寫在一個地方。你可能會說那麼咱們再加一層Manger來管理sectionModel的數組,而後把tableView的數據源和代理設置爲manager,而後在manager內部實現dataSourcedelegate解析sectionModel的數組展現界面。這樣一來咱們只須要配置sectionModel就能夠了。可是這樣作那麼萬一咱們在控制器上想要監聽tableView的滑動呢?思來想去,我最後嘗試用消息轉發 + 斷言的方式來嘗試解決這個問題。

消息轉發實現代理一對多

代理是一對一的,通知是一對多的。

剛開始學iOS的時候,咱們確定都聽過這樣一句話,來描述代理和通知的不一樣。可是其實經過消息轉發,咱們也是能夠來實現代理的一對多的。

若是對消息轉發沒啥概念的能夠看看這篇博客。簡單理解就是當調用方法的時候,系統經過isa指針層層查找方法列表,找不到方法的時候,在報找不到方法以前,系統還額外提供了幾個方法提供給咱們去實現這個方法。

代理一對多的主要實現的邏輯:

  • 1.提供一個delegate容器,存放代理。

  • 2.- (BOOL)respondsToSelector:(SEL)aSelector判斷代理容器裏的代理若是實現了代理方法,這個方法須要返回YES。若是返回NO,系統就斷定沒有實現代理方法,那麼就不會調用方法,那麼也就不會有後面的一系列的流程了。

    1. - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector返回方法的簽名。不返回簽名後面的消息轉發方法也不會調用。
    1. - (void)forwardInvocation:(NSInvocation *)anInvocation方法裏遍歷delegate容器,轉發方法。

斷言

斷言 (assertion) 在 Cocoa 開發裏通常用來在檢查輸入參數是否知足必定條件,並對其進行「論斷」。這是一個編碼世界中的哲學問題,咱們代碼的使用者 (有多是別的程序員,也有多是將來的本身) 很難作到在不知道實現細節的狀況下去對本身的輸入進行限制。大多數時候編譯器能夠幫助咱們進行輸入類型的檢查,可是若是代碼須要在特定的輸入條件下才能正確運行的話,這種更細緻的條件就難以控制了。在超過邊界條件的輸入的狀況下,咱們的代碼可能沒法正確工做,這就須要咱們在代碼實現中進行一些額外工做。

上面這段介紹是從喵神一篇斷言tips裏的摘抄。在不少的第三方庫中你確定也見過相似好比AFNetworking中隨便一搜NSAssert(NO, @"State method should never be called in the actual dummy class");相似的斷言很是常見。簡單理解當咱們輸入一個不合法參數的狀況的時候,程序就直接崩潰了,而且打印了斷言裏的描述。那麼咱們一眼就能知道咱們的輸入出問題了,而且問題出在哪裏。

這裏咱們爲何用斷言,因爲我內部實現了某些數據源和代理。那麼咱們確定不但願外部再實現這些實現過的方法。那麼咱們確定須要作一些約束,這裏用斷言顯然是最合適的。若是外部實現了實現過的方法,直接崩潰而且打印提示信息。

3. 方便切換

相似三行代碼實現設置界面的方案內部定義幾種樣式的方案,若是咱們想要把咱們已經寫好的界面切換到這種方案下,代價相對仍是比較大的。咱們項目中確定也實現了一些cell,我但願我以前的cell能無縫的接入進去。針對這種狀況我在rowModel裏添加了一個cellClass屬性來指定cell,和一個displayCellHandle block來設置cell的一些樣式。

瞄一眼寫法

[self.tableView zhn_initializeEnvironmentWithDefaultRowHeight:44
                                                 defaultCellClass:[NormalSettingTableViewCell class]
                                             defaultSectionHeader:nil
                                              defaultHeaderHeight:20
                                             defaultSectionFooter:nil
                                              defaultFooterHeight:0
                                                 originalDelegate:self
                                               originalDatasource:self];
    
    [self.tableView zhn_addSection:^(ZHNStaticTableSection *section) {
        [section zhn_addRow:^(ZHNStaticTableRow *row) {
            row.displayCellHandle = ^(UITableView *tableView, UITableViewCell *cell, NSIndexPath *indexPath) {
                cell.textLabel.text = @"帳號與安全";
            };
            row.selectCellHandle = ^(UITableView *tableView, NSIndexPath *indexPath) {
                NSLog(@"帳戶與安全");
            };
        }];
    }];
    
    [self.tableView zhn_addSection:^(ZHNStaticTableSection *section) {
        [section zhn_addRow:^(ZHNStaticTableRow *row) {
            row.displayCellHandle = ^(UITableView *tableView, UITableViewCell *cell, NSIndexPath *indexPath) {
                cell.textLabel.text = @"新消息通知";
            };
        }];
        [section zhn_addRow:^(ZHNStaticTableRow *row) {
            row.displayCellHandle = ^(UITableView *tableView, UITableViewCell *cell, NSIndexPath *indexPath) {
                cell.textLabel.text = @"隱私";
            };
        }];
        [section zhn_addRow:^(ZHNStaticTableRow *row) {
            row.displayCellHandle = ^(UITableView *tableView, UITableViewCell *cell, NSIndexPath *indexPath) {
                cell.textLabel.text = @"通用";
            };
        }];
    }];
複製代碼

總結

代碼在這裏 github.com/zhnnnnn/ZHN…

這是個人方案,還沒來得及在實際的項目中使用過。拋磚引玉,但願你們可以不吝賜教。我也很想知道大型知名項目裏你們都是怎麼寫這部分代碼的。

相關文章
相關標籤/搜索