推广

UITableView 建模 — 提高代码复用

iseeyu2年前 (2024-02-21)推广124

tableview 是开发中项目中常用的视图控件,并且是重复的使用,布局类似,只是数据源及Cell更改,所以会出现很多重复的内容,并且即使新建一个基础的列表也要重复这些固定逻辑的代码,这对于开发效率很不友好。
本文的重点是抽取重复的逻辑代码简化列表页面的搭建,达到数据驱动列表

思路草稿

说明:
首先tableview有两个代理delegate 和 datasource(基于单一职责设计规则)
delegate :负责交互事件;
datasource :负责cell创建及数据填充,这也是本文探讨的重点。
(1)基本原则
苹果将tableView的数据通过一个二维数组构建(组,行),这是一个很重要的设计点,要沿着这套规则继续发展,设计模式的继承,才是避免坏代码产生的基础。
(2)组
“组”是这套逻辑的根基先有组再有行,并且列表动态修改的内容都是以为基础,的结构相对固定,因此本文将抽离成一个数据模型而不是接口

#import <Foundation/Foundation.h>
#import "RWCellViewModelProtocol.h"

@interface RWSectionModel : NSObject
/// item数组:元素必须是遵守RWCellViewModel协议
@property (nonatomic, strong) NSMutableArray <id<RWCellViewModel>>*itemsArray;

/// section头部高度
@property (nonatomic, assign) CGFloat  sectionHeaderHeight;
/// section尾部高度
@property (nonatomic, assign) CGFloat  sectionFooterHeight;
/// sectionHeaderView: 必须是UITableViewHeaderFooterView或其子类,并且遵循RWHeaderFooterDataSource协议
@property (nonatomic, strong) Class headerReuseClass;
/// sectionFooterView: 必须是UITableViewHeaderFooterView或其子类,并且遵循RWHeaderFooterDataSource协议
@property (nonatomic, strong) Class footerReuseClass;

/// headerData
@property (nonatomic, strong) id headerData;
/// footerData
@property (nonatomic, strong) id footerData;
@end

(2)行
最核心的有三大CellCell高度Cell数据
这次的设计参考MVVM设计模式,对于行的要素提取成一个ViewModel,并且ViewModel要做成接口的方式,因为行除了这三个基本的元素外,可能要需要Cell填充的数据,比如titleString,subTitleString,headerImage等等,这样便于扩展。

#ifndef RWCellViewModel_h
#define RWCellViewModel_h

@import UIKit;

@protocol RWCellViewModel <NSObject>
/// Cell 的类型
@property (nonatomic, strong) Class cellClass;
/// Cell的高度:  0 则是UITableViewAutomaticDimension
@property (nonatomic, assign) CGFloat  cellHeight;
@end

#endif /* RWCellViewModel_h */

(3)tableView
此处不用使用tableViewController的方式,而使用view的方式,这样嵌入更方便。并且对外提供基本的接口,用于列表数据的获取,及点击事件处理。

备注:
关于数据,这里提供了多组和单组的两个接口,为了减少使用的过程中外部新建RWSectionModel这一步,但是其内部还是基于RWSectionModel这一个模型。

#import <UIKit/UIKit.h>
#import "RWCellViewModelProtocol.h"
#import "RWSectionModel.h"

@protocol RWTableViewDelegate;

@interface RWTableView : UITableView
/// rwdelegate
@property (nonatomic, weak) id<RWTableViewDelegate> rwdelegate;

/// 构建方法
/// @param delegate 是指rwdelegate
- (instancetype)initWithDelegate:(id<RWTableViewDelegate>)delegate;

@end


@protocol RWTableViewDelegate <NSObject>
@optional
/// 多组构建数据
- (NSArray <RWSectionModel*>*)tableViewWithMutilSectionDataArray;

/// 单组构建数据
- (NSArray <id<RWCellViewModel>>*)tableViewWithSigleSectionDataArray;


/// cell点击事件
/// @param data cell数据模型
/// @param indexPath indexPath
- (void)tableViewDidSelectedCellWithDataModel:(id)data indexPath:(NSIndexPath *)indexPath;
RWTableview.m

#pragma mark - dataSource
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    /// 数据源始终保持“二维数组的状态”,即SectionModel中包裹items的方式
    if ([self.rwdelegate respondsToSelector:@selector(tableViewWithMutilSectionDataArray)]) {
        self.dataArray = [self.rwdelegate tableViewWithMutilSectionDataArray];
        return self.dataArray.count;
    }
    else if ([self.rwdelegate respondsToSelector:@selector(tableViewWithSigleSectionDataArray)]) {
        RWSectionModel *sectionModel = [[RWSectionModel alloc]init];
        sectionModel.itemsArray = [self.rwdelegate tableViewWithSigleSectionDataArray].mutableCopy;
        self.dataArray = @[sectionModel];
        return 1;
    }
    return 0;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    RWSectionModel *sectionModel = [self.dataArray objectAtIndex:section];
    return sectionModel.itemsArray.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    /// 此处只做Cell的复用或创建
    RWSectionModel *sectionModel = [self.dataArray objectAtIndex:indexPath.section];
    id<RWCellViewModel>cellViewModel = [sectionModel.itemsArray objectAtIndex:indexPath.row];
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:NSStringFromClass(cellViewModel.cellClass)];
    if (cell == nil) {
        cell = [[cellViewModel.cellClass alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:NSStringFromClass(cellViewModel.cellClass)];
    }
    cell.selectionStyle = UITableViewCellSelectionStyleNone;
    return cell;
}

(4)Cell上子控件的交互事件处理
腾讯QQ部门的大神峰之巅提供了一个很好的解决办法,基于苹果现有的响应链(真的很牛逼),将点击事件传递给下个响应者,而不需要为事件的传递搭建更多的依赖关系。这是一篇鸡汤文章,有很多营养,比如tableview模块化,这也是我接下来要学习的。

#import <UIKit/UIKit.h>
#import "RWEvent.h"

@interface UIResponder (RWEvent)

- (void)respondEvent:(NSObject<RWEvent> *)event;

@end
#import "UIResponder+RWEvent.h"

@implementation UIResponder (RWEvent)

- (void)respondEvent:(NSObject<RWEvent> *)event {
    [self.nextResponder respondEvent:event];
}

@end

2020年11月18日 更新

鉴于此tableView封装在实际项目遇到的题进行改善,主要内容如下:
(1)使用分类的方式替换协议
优点:分类能更便捷的扩展原有类,并且使用更方便,不需要再导入协议文件及遵守协议
【RWCellDataSource协议】替换成:【UITableViewCell (RWData)】
【RWHeaderFooterDataSource协议】 替换成:【UITableViewHeaderFooterView (RWData)】

(2)cell高度缓存的勘误
willDisplayCell:中要想获取准确的Cell高度,那么必须在heightForRowAtIndexPath:方法中给Cell赋值,因为系统计算Cell的高度是在这个方法中进行的

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    RWSectionModel *sectionModel = [self.dataArray objectAtIndex:indexPath.section];
    id<RWCellViewModel>cellViewModel = [sectionModel.itemsArray objectAtIndex:indexPath.row];
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:NSStringFromClass(cellViewModel.cellClass)];
    /// Cell创建
    if (cell == nil) {
        cell = [[cellViewModel.cellClass alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:NSStringFromClass(cellViewModel.cellClass)];
    }
    /// Cell赋值
    [cell rw_setData:cellViewModel];
    cell.selectionStyle = UITableViewCellSelectionStyleNone;
    return cell;
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    RWSectionModel *sectionModel = [self.dataArray objectAtIndex:indexPath.section];
    id<RWCellViewModel>cellViewModel = [sectionModel.itemsArray objectAtIndex:indexPath.row];
    return cellViewModel.cellHeight ? : UITableViewAutomaticDimension;
}

- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
    RWSectionModel *sectionModel = [self.dataArray objectAtIndex:indexPath.section];
    id<RWCellViewModel>cellViewModel = [sectionModel.itemsArray objectAtIndex:indexPath.row];
    /// 高度缓存
    /// 此处高度做一个缓存是为了高度自适应的Cell,避免重复计算的工作量,对于性能优化有些帮助
    /// 如果想要在willDisplayCell获取到准确的Cell高度,那么必须在cellForRowAtIndexPath:方法给Cell赋值
    /// 同时可以避免由于高度自适应导致Cell的定位不准确,比如置顶或者滑动到某一个Cell的位置
    /// 如果自动布局要更新高度,可以将cellViewModel设置为0
    cellViewModel.cellHeight = cell.frame.size.height;
}

完整代码

特别感谢以下作者写的文章,给我很多启发

峰之巅:iOS 高效开发解决方案

donggelaile:一站式搭建各种滑动列表(Objective-C)

基于MVVM,用于快速搭建设置页,个人信息页的框架

利用MVVM设计快速开发个人中心、设置等模块

如何优雅的插入广告

iOS面向切面的TableView-AOPTableView

iOS面向切面的TableView-AOPTableView

扫描二维码推送至手机访问。

版权声明:本文由西安泽虎代运营发布,如需转载请注明出处。

转载请注明出处https://0291.com.cn/post/56616.html

相关文章

seo优化不能忽略的几个细节。

seo优化不能忽略的几个细节。

比如必定要含有关键词才行,这就要求优化人员有必要具有必定的文学功底,去组合这些关键词构成标题,一些优化人员在这方面简略失掉标准,所起的标题常常发生关键词的堆砌现象,这样的标题是算法所不允许的。所以这种现象也就免不了搜索引擎的赏罚,毕竟导致网站降权,排名下降等现象。所以要注意网站标题要天然的含有关键词...

详解七大因素,什么正在影响百度搜索结果排序。

详解七大因素,什么正在影响百度搜索结果排序。

前不久看到李国威老师的一篇文章“公关部的第一职能是什么?”,其中提到一线公关人最大的烦恼是老板,颇有感触。 有一种KPI叫老板可见度,比如老板的朋友圈,老板的百度首页,公关有时候会迫于满足“老板的期待”,刚发了一篇新闻稿,老板可能会马上来问,为什么搜公司的名字百度首页还看不到,为什么稿子...

产品推广服务协议

产品推广服务协议

产品推广服务协议(简称)是根据《中华人民共和国合同法》的有关规定,为实现共同目标,维护各方合法权益,依法签订的买卖合同。本协议为甲乙双方共同制定并执行的书面合同协议。甲方(简称甲方):甲乙双方本着平等、自愿、互利的原则,本着互惠互利的原则,就甲方销售产品活动中所涉及的技术及服务事宜达成一致...

从0到3000万,产业B2B的运营怎么做?

从0到3000万,产业B2B的运营怎么做?

对在线交易而言,这是一个简单的时代,亦是一个复杂的时代。   媒 体与资本的嗅觉在互联网圈里永远是最灵敏的,最近陆续接到媒体、机构的邀约基本都是谈B2B,聊产业互联网的。说“山重水复O2O,柳暗花明B2B”还真 的一点都不夸张,2016年是B2B的元年,从沉寂多年...

教你网站TKD标签的正规写法是什么。

教你网站TKD标签的正规写法是什么。

站内优化对于seo是非常重要,小编搜索引擎优化习气性地把它们称作为门面性的作业。以往的博文主要是讲了搜索引擎的作业原理,以及为了迎合搜索引擎所做的一些列站内优化作业。尽管TKD也作为站内优化的一部分,网站地图但SEOER们却习气性地把它们独立开来,作为一个非常重要的部分要点关注。检验一个SEOER...

怎么上拼多多卖货?看这篇就够了!

怎么上拼多多卖货?看这篇就够了!

随着电商的发展,越来越多的创业者选择在拼多多这个平台上卖货。但是,如何才能成功上拼多多卖货呢?别急,本文将为你揭秘。 我们要做好充分的准备工作。在决定上拼多多卖货之前,你需要先了解这个平台的规则和机制,以便更好地适应和利用。同时,你也需要确定你要销售的商品,并准备好相关的商品资料,如...

现在,非常期待与您的又一次邂逅

我们努力让每一部企业宣传片和抖音短视频成为商业大片