優雅的使用UITableView

iOS面試簡歷專員發表於2020-05-08

痛點

在我們iOS開發中 UITableView幾乎是所有App都會使用的一個UI控制元件,因為業務的需要,我們常常會註冊多種Cell,然後在

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath

中就會很自然的寫出一堆類似這樣的程式碼:

優雅的使用UITableView

事件處理的程式碼大概是這樣的:

優雅的使用UITableView

這似乎沒有什麼問題,程式碼很乾淨,邏輯也比較清晰。

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

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

如果使用 indexPath作為判斷條件,如果你的cell順序有變化,或者有改動,那麼你可能至少需要維護以下幾個地方:

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

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

那有沒有什麼好的方法處理這類程式碼?

分析

其實我們仔細想想,無論一個多麼複雜的 UITableView,與之對應的其實只要一個 模型陣列

那麼我們如果維護好了 模型陣列,是不是就維護好了 UITableView中所有的cell,這是顯而易見的。

如果我們的 UITableView中有 N種cell樣式,那麼 模型陣列中肯定也會有 N種模型。

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

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

想一想展示一個 UITableView的過程

  1. 發起網路請求
  2. JSON to Model,構造 模型陣列
  3. 資料填充

大致就是這三步吧。

其實在第二步構造 模型陣列時,我們是不是就可以確定好UI的樣式了?

如果這裡想不明白,再看看我們上面的分析,一種 cell樣式對應著一種 模型,那麼我們知道了 模型,是不是就知道了 cell樣式

如果你還是不大清楚,那們就進入實戰部分

實戰

優雅的使用UITableView

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

這個介面需要 UITableView

沒錯,這個介面在 UIViewController中直接構建就可以了。

請再看下面

優雅的使用UITableView
優雅的使用UITableView

是不是感覺都很類似,但是又有很多不同的地方。

方案

  1. 一個一個VC的寫。

缺點:

有很多重複程式碼,而且後期的改動需要維護的地方,做不到高內聚。

  1. 抽象一個父類

    缺點:

    雖然三個VC看似UI上有很多共同之處,但是其中的業務處理完全不同的

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

    缺點:

    這種方案看似很好了,但是你看如果在一個介面中,如果新增一個或者減少一個控制元件,又得重新做約束了,這也顯然不是我們想要的。

下面看看透過 UITableView構建的UI

展示

優雅的使用UITableView

SignInVC 中的程式碼:

優雅的使用UITableView
優雅的使用UITableView

PasswordSignVC 中的程式碼:

優雅的使用UITableView

再看cell的dequeue程式碼

優雅的使用UITableView

資料的繫結,全部分散到了每個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;@endNS_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事件繫結程式碼:

優雅的使用UITableView

這裡我們還是要用一個協議的:

優雅的使用UITableView

注意

用這個協議主要是方便程式碼的閱讀,而且在Swift中是必須使用協議的,因為編譯時找不到這個方法。

可以看到ButtonCell的程式碼中並沒有這樣一段程式碼

@property (nonatomic, weak) id<ButtonCellActionable> delegate;

或者

@property (nonatomic, strong) void (^buttonAction)(void);

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

尾巴

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

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


優雅的使用UITableView
優雅的使用UITableView

Demo

推薦?:

如果你想一起進階,不妨新增一下交流群 1012951431

面試題資料或者相關學習資料都在群檔案中 進群即可下載!

優雅的使用UITableView


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69971523/viewspace-2690586/,如需轉載,請註明出處,否則將追究法律責任。

相關文章