我用一个 UITableView,干掉了 80% 复杂页面

一.引言

在 iOS 开发中,"复杂页面"几乎是所有人都会遇到的问题。

比如一个典型的专家详情页,通常包含:

  • 用户信息
  • 数据统计(胜率、收益)
  • 图表
  • 推荐内容
  • 历史列表

这个页面中倒是包含了列表,所以大家会考虑到UITableView,有些时候可能并不包含复用列表这时候大家就很难想到UITableView。

很多人的写法是这样的:

  • UIScrollView + 多个子 View
  • 各种 if/else 控制显示隐藏
  • 需求一改,牵一发动全身

写到后期,往往会变成一个"谁都不敢动"的页面。

二. 问题的本质:采用了不合理的方式描述页面

出现这种现象的原因主要是因为,大多数人对页面的理解是这样的:页面 = 一堆 View 的组合。

但实际上,这类页面更接近:页面 = 多个"区块(Section)"的组合。

一旦我们换成这个角度来理解页面,那么实现的方式将会完全不同。

三. 核心思路:把页面构建成Section 列表

我在项目中采用了一种非常简单但有效的方式,首先我的项目中会有两个类在通用组件内,我们以其中一个 PHBaseSectionItem****为例:

Swift 复制代码
import UIKit

open class PHBaseSectionItem: NSObject {
    
    /// cell类
    public var tabCellClass: UITableViewCell.Type?
    /// cell类
    public var collectionCellClass: UICollectionViewCell.Type?
    /// cell标识
    public  var cellIdentifier: String = ""
    /// 组头标识
    public var headerIdentifier: String = ""
    /// 组标题
    public var title: String = ""
    /// 子列表数据
    public var subItems: [Any] = []
    /// 子列表size
    public var itemSize: CGSize = CGSize.zero
    /// 组头高度
    public var headerHeight: CGFloat = 0
    /// 组尾高度
    public var footerHeight: CGFloat = 0
    

}

我的这个类可以用在UITableView列表中,也同样可以使用在UICollectionView列表中,我们就以UITableView为例:

  • **tabCellClass:**UITableViewCell.Type UITableViewCell,的类型。
  • **cellIdentifier:**字符串,该组UITableViewCell的标识。
  • **headerIdentifier:**字符串,该组组头的标识。
  • **title:**字符串,该组的标题。
  • **subItems:**Any类型数组,如果页面是多类型的列表会用到这个。
  • **headerHeight:**CGFloat,该组组头高度。
  • **footerHeight:**CGFloat,该组组尾高度。

然后在页面内创建一个页面结构的数据列表:

Swift 复制代码
/// 页面结构
private var dataList: [PHBaseSectionItem] = []

这些东西出来之后,页面将不再由View决定,而是由一组 PHBaseSectionItem决定。

页面结构示意图:

我们把示例简化一点就不加图表了哈,你可以把整个页面理解为:

  1. 用户信息
  2. 统计数据
  3. 最新方案列表
  4. 历史方案列表

四. 关键设计:引用 Builder 统一构建页面结构

然后我们引入一个 Builder它主要用来,根据我们需要显示或者隐藏的内容来构建列表数据 dataList:

Swift 复制代码
class PHContributorDetailBuilder: NSObject {

    /// 构建专家详情列表数据
    static func buildDataList()->[PHSectionItem] {
        var list:[PHSectionItem] = []
       // 信息
        let infoSectionItem = PHSectionItem()
        infoSectionItem.cellIdentifier = "PHContributorInfoCell"
        infoSectionItem.tableViewCellClass = PHContributorInfoCell.self
        list.append(infoSectionItem)
        // 近期战绩
        let lastRecordSectionItem = PHSectionItem()
        lastRecordSectionItem.cellIdentifier = "PHContributorRecordCell"
        lastRecordSectionItem.tableViewCellClass = PHContributorRecordCell.self
        list.append(lastRecordSectionItem)
        // 在售方案
        let saleSectionItem = PHSectionItem()
        saleSectionItem.cellIdentifier = "PHSchemeDetailOtherCell"
        saleSectionItem.tableViewCellClass = PHSchemeDetailOtherCell.self
        saleSectionItem.headerIdentifier = "PHContributorSectionHeaderView"
        saleSectionItem.headerHeight = 44
        saleSectionItem.title = "Latest Insights"
        list.append(saleSectionItem)
        
        // 历史方案
        let historySectionItem = PHSectionItem()
        historySectionItem.cellIdentifier = "PHSchemeDetailOtherCell"
        historySectionItem.tableViewCellClass = PHSchemeDetailOtherCell.self
        historySectionItem.headerIdentifier = "PHContributorSectionHeaderView"
        historySectionItem.headerHeight = 44
        historySectionItem.title = "Past Insights"
        list.append(historySectionItem)
        
        
        return list
    }
    
}

Builder 负责:

  • 定义页面有哪些区块
  • 每个区块用什么 Cell
  • Header 长什么样,Header高度等等。

五. tableView 的角色:不是核心,而是"渲染器"

在这套架构下,UITableView 的职责被极度简化:

Swift 复制代码
private let tableView = UITableView(frame: .zero, style: .plain)

它只做三件事:

  1. 注册 Cell(根据 dataList)
  2. 渲染 Cell(根据 dataList)
  3. 刷新 UI(reloadData)

UITableView 不再是"列表控件",而是一个"页面渲染引擎"。

六. 如何处理"特殊区块"

在我们的这个页面中,有两个列表属于不同于其它类型的,但是又不多,这种情况我们就直接在视图控制器内,通过sectionItem的标识或者标题,来单独处理这两组数据了。

例如我的判断是:

Swift 复制代码
if sectionItem.title == "最新方案" {
    return schemeList.count
}

if sectionItem.title == "历史方案" {
    return historySchemeList.count
}

而如果确实有很多不同的列表,大家也看见了当前PHBaseSectionItem是Open修饰的,也就是我们具备最大的权限,完全可以继承自PHBaseSectionItem创建一个比较个性的数据结构,每个结构中都有可以变化的数据,其实在PHBaseSectionItem也提供了自列表的入口,大家也可以直接使用,只是因为是Any类型,需要进行的判断会多一些。

整体策略还是:

统一骨架 + 局部特判

七. 这套方案带来的工程价值

1. 页面不会越改越乱:

结构由 Builder 控制,而不是 VC。避免 if/else 爆炸

2. 新增模块成本极低:

新增一个区块,只需要:增加一个 SectionItem,不需要改动页面整体结构。

3. 天然支持"动态页面":

比如:

  • AB Test
  • 不同用户展示不同模块

只需要改 Builder。

4. ViewController 复杂度被锁死:

VC 只负责:

  • 渲染
  • 绑定数据
  • 响应事件

不会膨胀成 1000+ 行的"上帝类"

八. 结语

我们并没有在"优化 UITableView 的写法", 而是在重新定义"页面应该如何被描述"。

过去我们用 View 拼页面,现在我们用 Section 描述页面。

当页面结构被抽象成数据之后:

    • UITableView 只是一个渲染工具
    • ViewController 不再承载结构复杂度
    • 页面也不会随着需求迭代而失控

UITableView 没变, 但我们看待它的方式变了。

而这,才是这套方案真正的价值。

相关推荐
T1an-12 小时前
最右IOS开发A卷笔试题3.31
c++·ios
小江的记录本2 小时前
【Spring注解】Spring生态常见注解——面试高频考点总结
java·spring boot·后端·spring·面试·架构·mvc
大新新大浩浩2 小时前
Deerflow部署-X86架构-在ubuntu2204操作系统上使用docker模式部署
docker·容器·架构
斯普信专业组3 小时前
Kubeasz快速部署k8s混合架构集群
java·架构·kubernetes
无忧智库3 小时前
零信任安全体系:从“围墙城堡”到“零信任动态管控”的架构演进与实战洞察(PPT)
安全·架构
Coder个人博客3 小时前
10_apollo_docker_scripts子模块软件架构分析
架构
Coder个人博客3 小时前
08_apollo_scripts_scripts子模块软件架构分析
架构
Coder个人博客3 小时前
09_apollo_docker_build子模块软件架构分析文档
架构
wzl202612133 小时前
《从协议层对抗折叠:iPad协议脚本在企微批量群发中的集成与优化》
ios·企业微信·ipad