You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何创建可复用TableView实现订单应用的两种商品类型

订单应用商品类型复用实现方案

基于你已实现的Type One,通过组件化拆分+嵌套TableView的方式复用逻辑和视图,完全避免重复代码,具体实现步骤如下:

1. 抽象统一数据模型

先把两种商品的共性和差异抽象成层级模型,让主TableView能统一处理:

  • 基类BaseGoodsModel:定义通用属性(如商品ID)和通用方法(如返回对应Cell类型)
  • NormalGoodsModel(对应Type One):继承基类,添加描述、规格、选项等专属属性,指定对应的Cell类型
  • CombinationGoodsModel(对应Type Two):继承基类,添加顶部商品类型标题,以及存储嵌套普通商品的数组属性
class BaseGoodsModel {
    var goodsId: String
    var cellType: UITableViewCell.Type {
        return BaseGoodsCell.self
    }
    
    init(goodsId: String) {
        self.goodsId = goodsId
    }
}

class NormalGoodsModel: BaseGoodsModel {
    var desc: String
    var spec: String
    var options: [String]
    
    override var cellType: UITableViewCell.Type {
        return NormalGoodsCell.self
    }
    
    init(goodsId: String, desc: String, spec: String, options: [String]) {
        self.desc = desc
        self.spec = spec
        self.options = options
        super.init(goodsId: goodsId)
    }
}

class CombinationGoodsModel: BaseGoodsModel {
    var typeTitle: String
    var nestedGoodsList: [NormalGoodsModel]
    
    override var cellType: UITableViewCell.Type {
        return CombinationGoodsCell.self
    }
    
    init(goodsId: String, typeTitle: String, nestedGoodsList: [NormalGoodsModel]) {
        self.typeTitle = typeTitle
        self.nestedGoodsList = nestedGoodsList
        super.init(goodsId: goodsId)
    }
}

2. 复用Type One的Cell与业务逻辑

你已实现的NormalGoodsCell直接保留,同时把Type One的交互逻辑(比如选项选择、规格变更)抽离到NormalGoodsModel的扩展或独立工具类中,避免和Cell耦合,方便后续嵌套时调用。

3. 实现组合商品Cell(嵌套TableView)

创建CombinationGoodsCell,顶部放商品类型标题,下方嵌套一个小型TableView,这个嵌套TableView直接复用NormalGoodsCell和对应的数据源逻辑:

class CombinationGoodsCell: UITableViewCell {
    private lazy var typeTitleLabel: UILabel = {
        let label = UILabel()
        label.font = UIFont.boldSystemFont(ofSize: 16)
        label.textColor = .black
        return label
    }()
    
    private lazy var nestedTableView: UITableView = {
        let tableView = UITableView(frame: .zero, style: .plain)
        tableView.register(NormalGoodsCell.self, forCellReuseIdentifier: "NormalGoodsCell")
        tableView.delegate = self
        tableView.dataSource = self
        tableView.isScrollEnabled = false // 禁用嵌套滚动,避免和主TableView冲突
        tableView.separatorStyle = .none
        return tableView
    }()
    
    private var goodsList: [NormalGoodsModel] = []
    
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        setupSubviews()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    private func setupSubviews() {
        contentView.addSubview(typeTitleLabel)
        contentView.addSubview(nestedTableView)
        
        typeTitleLabel.snp.makeConstraints { make in
            make.top.left.right.equalToSuperview().inset(16)
            make.height.equalTo(24)
        }
        
        nestedTableView.snp.makeConstraints { make in
            make.top.equalTo(typeTitleLabel.snp.bottom).offset(12)
            make.left.right.bottom.equalToSuperview().inset(16)
        }
    }
    
    func configure(with model: CombinationGoodsModel) {
        typeTitleLabel.text = model.typeTitle
        goodsList = model.nestedGoodsList
        nestedTableView.reloadData()
        
        // 计算嵌套TableView总高度并更新约束
        let totalHeight = goodsList.reduce(0) { $0 + $1.cellHeight }
        nestedTableView.snp.updateConstraints { make in
            make.height.equalTo(totalHeight)
        }
    }
}

// 嵌套TableView代理/数据源,直接复用Type One的逻辑
extension CombinationGoodsCell: UITableViewDelegate, UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return goodsList.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "NormalGoodsCell", for: indexPath) as! NormalGoodsCell
        cell.configure(with: goodsList[indexPath.row])
        // 通过闭包把嵌套Cell的交互事件传递给主Controller
        cell.onOptionSelected = { [weak self] option in
            self?.nestedOptionSelected?(option)
        }
        return cell
    }
    
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return goodsList[indexPath.row].cellHeight
    }
    
    // 定义闭包传递嵌套Cell的交互事件
    var nestedOptionSelected: ((String) -> Void)?
}

4. 主TableView统一管理两种商品类型

主TableView的数据源数组用[BaseGoodsModel]类型,在代理方法中根据模型的cellType创建对应Cell,实现统一渲染:

class OrderViewController: UIViewController {
    private lazy var tableView: UITableView = {
        let tableView = UITableView(frame: view.bounds, style: .grouped)
        tableView.register(NormalGoodsCell.self, forCellReuseIdentifier: "NormalGoodsCell")
        tableView.register(CombinationGoodsCell.self, forCellReuseIdentifier: "CombinationGoodsCell")
        tableView.delegate = self
        tableView.dataSource = self
        return tableView
    }()
    
    private var goodsData: [BaseGoodsModel] = []
    
    override func viewDidLoad() {
        super.viewDidLoad()
        view.addSubview(tableView)
        loadMockData()
    }
    
    private func loadMockData() {
        // 模拟普通商品数据
        let normalGoods = NormalGoodsModel(goodsId: "1", desc: "普通商品描述", spec: "规格:中号", options: ["选项A", "选项B"])
        // 模拟组合商品数据
        let comboGoods = CombinationGoodsModel(goodsId: "2", typeTitle: "组合套餐A", nestedGoodsList: [normalGoods])
        goodsData = [normalGoods, comboGoods]
        tableView.reloadData()
    }
}

extension OrderViewController: UITableViewDelegate, UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return goodsData.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let model = goodsData[indexPath.row]
        let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: model.cellType), for: indexPath)
        
        if let normalCell = cell as? NormalGoodsCell {
            normalCell.configure(with: model as! NormalGoodsModel)
            normalCell.onOptionSelected = { option in
                // 处理普通商品选项选择
                print("选中普通商品选项:\(option)")
            }
        } else if let comboCell = cell as? CombinationGoodsCell {
            comboCell.configure(with: model as! CombinationGoodsModel)
            comboCell.nestedOptionSelected = { option in
                // 处理组合商品嵌套选项选择
                print("选中组合商品嵌套选项:\(option)")
            }
        }
        
        return cell
    }
    
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        let model = goodsData[indexPath.row]
        if let comboModel = model as? CombinationGoodsModel {
            // 组合商品Cell高度 = 标题高度 + 嵌套商品总高度 + 内边距
            return 24 + 12 + comboModel.nestedGoodsList.reduce(0) { $0 + $1.cellHeight } + 16
        }
        return (model as! NormalGoodsModel).cellHeight
    }
}

5. 优化建议

  • 把Cell高度计算逻辑封装到对应模型中,避免Controller中出现大量高度计算代码
  • 统一通过闭包或代理传递交互事件,保证业务逻辑集中在主Controller处理
  • 若需动态高度,主TableView和嵌套TableView均可使用UITableView.automaticDimension,前提是Cell的约束设置完整

内容的提问来源于stack exchange,提问作者Joe

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 05:07:41