154

优雅的使用UITableView(OC 上)

 6 years ago
source link: https://juejin.im/post/5a348a9b6fb9a0451a7672bb
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

优雅的使用UITableView(OC 上)

2018年01月14日 03:01 ·  阅读 12035

在我们iOS开发中UITableView几乎是所有App都会使用的一个UI控件,因为业务的需要,我们常常会注册多种Cell,然后在

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

中就会很自然的写出一堆类似这样的代码:

1605d4a14c71a6eb~tplv-t2oaga2asx-zoom-in-crop-mark:4536:0:0:0.awebp

事件处理的代码大概是这样的:

1605d53c49ddb381~tplv-t2oaga2asx-zoom-in-crop-mark:4536:0:0:0.awebp

这似乎没有什么问题,代码很干净,逻辑也比较清晰。

但是你维护几个版本之后,或者遇到了一个善变的产品经理。

你会发现,这样的代码维护起来真的很危险,稍微一不注意就出错了,这里用的type作为判断条件可能相较与indexPath要好一点。

如果使用indexPath作为判断条件,如果你的cell顺序有变化,或者有改动,那么你可能至少需要维护以下几个地方:

  1. 你的模型数组
  2. cell dequeue的判断条件
  3. 事件处理的判断条件

维护的东西越多,意味着你出错的概率是越大的。

那有没有什么好的方法处理这类代码?

其实我们仔细想想,无论一个多么复杂的UITableView,与之对应的其实只要一个模型数组

那么我们如果维护好了模型数组,是不是就维护好了UITableView中所有的cell,这是显而易见的。

如果我们的UITableView中有N种cell样式,那么模型数组中肯定也会有N种模型。

也就是说每种cell与每种模型是一一配对的,常规的模型与cell绑定是如上述的思路。

上述的思路,显然不是我们想要的,维护起来太不便,而且耦合性也比较大。

想一想展示一个UITableView的过程

  1. 发起网络请求
  2. JSON to Model,构造模型数组

大致就是这三步吧。

其实在第二步构造模型数组时,我们是不是就可以确定好UI的样式了?

如果这里想不明白,再看看我们上面的分析,一种cell样式对应着一种模型,那么我们知道了模型,是不是就知道了cell样式

如果你还是不大清楚,那们就进入实战部分

1605d6f612331030~tplv-t2oaga2asx-zoom-in-crop-mark:4536:0:0:0.awebp

先看这样一个简单的页面,你肯定会说:朋友,你TM在逗我们,这和UITableView有毛关系?

这个界面需要UITableView

没错,这个界面在UIViewController中直接构建就可以了。

请再看下面

1605d79ae2a602c7~tplv-t2oaga2asx-zoom-in-crop-mark:4536:0:0:0.awebp


1605d798372c3e95~tplv-t2oaga2asx-zoom-in-crop-mark:4536:0:0:0.awebp

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

  1. 一个一个VC的写。

缺点:

有很多重复代码,而且后期的改动需要维护的地方,做不到高内聚。

  1. 抽象一个父类

    缺点:

    虽然三个VC看似UI上有很多共同之处,但是其中的业务处理完全不同的

  2. 抽象一个UIHelper用于构建UI

    缺点:

    这种方案看似很好了,但是你看如果在一个界面中,如果添加一个或者减少一个控件,又得重新做约束了,这也显然不是我们想要的。

下面看看通过UITableView构建的UI

1605ddfb5d055b3d~tplv-t2oaga2asx-zoom-in-crop-mark:4536:0:0:0.awebp

SignInVC 中的代码:

1605de11b9753a6e~tplv-t2oaga2asx-zoom-in-crop-mark:4536:0:0:0.awebp


1605de3a0a7f67fd~tplv-t2oaga2asx-zoom-in-crop-mark:4536:0:0:0.awebp

PasswordSignVC 中的代码:

1605de6f892f1952~tplv-t2oaga2asx-zoom-in-crop-mark:4536:0:0:0.awebp

再看cell的dequeue代码

1605dea0fea46ebe~tplv-t2oaga2asx-zoom-in-crop-mark:4536:0:0:0.awebp

数据的绑定,全部分散到了每个cell中。

Row.h的代码

objectivec
#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的方法

erlang
- (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的代码中并没有这样一段代码

objectivec
@property (nonatomic, weak) id<ButtonCellActionable> delegate;
objectivec
@property (nonatomic, strong) void (^buttonAction)(void);

这样我们的ButtonCell不会和VC耦合,修改起来真的很方便

以上思路大概就介绍完了,这只是Detail部分,List部分我会在demo中给出

关于Detail和List的概念我会在第三节中介绍,第二节是Swift版的思路,Swift可以用到泛型,代码更优雅。

160f29c1d9d8696d~tplv-t2oaga2asx-zoom-in-crop-mark:4536:0:0:0.awebp


160f29c8f9efd6fd~tplv-t2oaga2asx-zoom-in-crop-mark:4536:0:0:0.awebp

Demo


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK