美文网首页iOS学习专题
优雅的使用UITableView

优雅的使用UITableView

作者: iOS开发面试题技术合集 | 来源:发表于2020-04-17 14:10 被阅读0次

    痛点

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

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

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

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

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

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

    你会发现,这样的代码维护起来真的很危险,稍微一不注意就出错了,这里用的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样式

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

    实战

    先看这样一个简单的页面,你肯定会说:朋友,你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

    推荐👇:

    如果你想一起进阶,不妨添加一下交流群1012951431

    面试题资料或者相关学习资料都在群文件中 进群即可下载!

    原文地址

    相关文章

      网友评论

        本文标题:优雅的使用UITableView

        本文链接:https://www.haomeiwen.com/subject/gfoevhtx.html