優雅的使用UITableView(OC 上)

痛點

在咱們iOS開發中UITableView幾乎是全部App都會使用的一個UI控件,由於業務的須要,咱們經常會註冊多種Cell,而後在html

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
複製代碼

中就會很天然的寫出一堆相似這樣的代碼:git

事件處理的代碼大概是這樣的: github

這彷佛沒有什麼問題,代碼很乾淨,邏輯也比較清晰。數組

可是你維護幾個版本以後,或者遇到了一個善變的產品經理。網絡

你會發現,這樣的代碼維護起來真的很危險,稍微一不注意就出錯了,這裏用的type做爲判斷條件可能相較與indexPath要好一點。app

若是使用indexPath做爲判斷條件,若是你的cell順序有變化,或者有改動,那麼你可能至少須要維護如下幾個地方:ide

  1. 你的模型數組
  2. cell dequeue的判斷條件
  3. 事件處理的判斷條件
  4. 。。。。

維護的東西越多,意味着你出錯的機率是越大的。ui

那有沒有什麼好的方法處理這類代碼?atom

分析

其實咱們仔細想一想,不管一個多麼複雜的UITableView,與之對應的其實只要一個模型數組spa

那麼咱們若是維護好了模型數組,是否是就維護好了UITableView中全部的cell,這是顯而易見的。

若是咱們的UITableView中有N種cell樣式,那麼模型數組中確定也會有N種模型。

也就是說每種cell與每種模型是一一配對的,常規的模型與cell綁定是如上述的思路。

上述的思路,顯然不是咱們想要的,維護起來太不便,並且耦合性也比較大。

想想展現一個UITableView的過程

  1. 發起網絡請求
  2. JSON to Model,構造模型數組
  3. 數據填充

大體就是這三步吧。

其實在第二步構造模型數組時,咱們是否是就能夠肯定好UI的樣式了?

若是這裏想不明白,再看看咱們上面的分析,一種cell樣式對應着一種模型,那麼咱們知道了模型,是否是就知道了cell樣式

若是你仍是不大清楚,那們就進入實戰部分

實戰

先看這樣一個簡單的頁面,你確定會說:朋友,你TM在逗咱們,這和UITableView有毛關係?

這個界面須要UITableView

沒錯,這個界面在UIViewController中直接構建就能夠了。

請再看下面

是否是感受都很相似,可是又有不少不一樣的地方。

方案

  1. 一個一個VC的寫。

缺點:

有不少重複代碼,並且後期的改動須要維護的地方,作不到高內聚。

  1. 抽象一個父類

    缺點:

    雖然三個VC看似UI上有不少共同之處,可是其中的業務處理徹底不一樣的

  2. 抽象一個UIHelper用於構建UI

    缺點:

    這種方案看似很好了,可是你看若是在一個界面中,若是添加一個或者減小一個控件,又得從新作約束了,這也顯然不是咱們想要的。

下面看看經過UITableView構建的UI

展現

SignInVC 中的代碼:

PasswordSignVC 中的代碼:

再看cell的dequeue代碼

數據的綁定,所有分散到了每一個cell中。

Row.h的代碼

#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

@protocol Updatable <NSObject>

@optional
- (void)updateViewData:(id)viewData;

@end


@interface Row : NSObject

@property(nonatomic, copy, readonly) NSString *reuseIdentifier;

@property(nonatomic, strong, readonly) Class cellClass;

@property(nonatomic, strong, readonly) id model;

- (instancetype)initWithClass:(Class)cls model:(id)model;

- (instancetype)initWithClass:(Class)cls;

- (void)updateCell:(UITableViewCell *)cell;

@end

NS_ASSUME_NONNULL_END
複製代碼

Row.m的代碼

@interface Row()

@property(nonatomic, strong, readwrite) Class cellClass;

@property(nonatomic, strong, readwrite) id model;

@end

@implementation Row

- (instancetype)initWithClass:(Class)cls {
    if (self = [self initWithClass:cls model:@""]) {
        
    }
    return self;
}

- (instancetype)initWithClass:(Class)cls model:(id)model {
    if (self = [super init]) {
        self.cellClass = cls;
        self.model = model;
    }
    return self;
}

- (void)updateCell:(UITableViewCell *)cell {
    if ([cell respondsToSelector:@selector(updateViewData:)]) {
        [cell performSelector:@selector(updateViewData:) withObject:self.model];
    }
}

- (NSString *)reuseIdentifier {
    return [NSString stringWithFormat:@"%@", self.cellClass];
}

@end
複製代碼

整個Row的代碼不過100行,把全部的處理都內聚在了一塊兒,咱們只要維護好模型數組就能很好的管理UITableView

UI是構建完成了,可是我相信其中有兩個問題你確定比較關心

  1. Cell 高度計算
  2. Cell上事件的回調

Cell 高度計算

在iOS8以後UITableView中推出了Self-sizing的功能,因此Cell的高度改變

UIView *dummyView = [[UIView alloc] init];
        dummyView.translatesAutoresizingMaskIntoConstraints = NO;
        [self.contentView insertSubview:dummyView belowSubview:self.textField];
        [dummyView.topAnchor constraintEqualToAnchor:self.contentView.topAnchor].active = YES;
        [dummyView.bottomAnchor constraintEqualToAnchor:self.contentView.bottomAnchor].active = YES;
        NSLayoutConstraint *constraint = [dummyView.heightAnchor constraintEqualToConstant:60];

        constraint.priority = 999;
        constraint.active = YES;
複製代碼

若是你對這塊不熟悉,請跳轉。若是你想對Auto Layout有一個提升建議看看Auto Layout Guide, 若是你想知道systemLayoutSizeFittingSize的做用,請看 深刻理解Auto Layout 第一彈

Cell上事件的回調

有人確定會不屑這裏,可是我想說:若是不用block代理觀察者

怎麼把cell上button的事件回調到VC中(button沒有暴露給外部)?

咱們先看添加Action的方法

- (void)addTarget:(nullable id)target action:(SEL)action forControlEvents:(UIControlEvents)controlEvents;
複製代碼

這裏須要這三個參數:

  • target(action的相應者)
  • action(點擊按鈕相應的方法)
  • controlEvents(這個通常爲UIControlEventTouchUpInside)

只要咱們找到了target,把action寫到target裏這個action綁定是否是就完成了。

target其實就是咱們的VC,咱們只要把VC傳遞給Cell便可,可是這樣是否是Cell又和VC耦合了啊。這個用block,delegate沒什麼區別吧!

如今咱們須要解決的問題就是找到Cell的VC,大功便可告成。

這是就須要一個重要的概念閃亮登場iOS響應鏈(Responder Chain)

這裏就不展開了,可是你必定要去了解這個。

響應鏈能夠解決的問題:

  • 擴大相應區域
  • 超出父類視圖相應依然能夠傳遞
  • 垮圖層傳遞事件

找到UIViewUIViewController

- (UIViewController *)viewController {
    UIResponder *responder = self;
    while (![responder isKindOfClass:[UIViewController class]]) {
        responder = [responder nextResponder];
        if (nil == responder) {
            break;
        }
    }
    return (UIViewController *)responder;
}
複製代碼

ButtonCell事件綁定代碼:

這裏咱們仍是要用一個協議的:

注意

用這個協議主要是方便代碼的閱讀,並且在Swift中是必須使用協議的,由於編譯時找不到這個方法。

能夠看到ButtonCell的代碼中並無這樣一段代碼

@property (nonatomic, weak) id<ButtonCellActionable> delegate;
複製代碼

或者

@property (nonatomic, strong) void (^buttonAction)(void);
複製代碼

這樣咱們的ButtonCell不會和VC耦合,修改起來真的很方便

尾巴

以上思路大概就介紹完了,這只是Detail部分,List部分我會在demo中給出

關於Detail和List的概念我會在第三節中介紹,第二節是Swift版的思路,Swift能夠用到泛型,代碼更優雅。

Demo

相關文章
相關標籤/搜索