优雅的使用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

相关文章
相关标签/搜索