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

iOS中UITableViewCell内动态状态节点视图实现方案问询

动态物流节点视图实现方案(集成到UITableViewCell)

嘿,我来帮你搞定这个动态节点视图的需求!之前用Storyboard做固定数量的版本没问题,但要适配API返回的动态节点数,这里有两个实用的方案,你可以根据需求选:


方案一:纯代码+Auto Layout动态创建视图(适合节点数较少的场景)

这个方案直接在自定义UITableViewCell里动态生成每个节点的圆和连接线,用Auto Layout确保布局自适应,步骤如下:

  1. 自定义UITableViewCell子类,先搭好基础的容器视图,用来统一管理所有节点元素
  2. 编写配置方法,接收API返回的状态数组,动态创建子视图
  3. 处理布局逻辑:第一个节点左对齐容器,后续节点和前一个保持固定间距,同时在中间添加连接线;最后一个节点不需要连接线
  4. 根据状态样式化:完成的节点用绿色,当前节点用蓝色,未完成的用灰色(你可以根据需求调整)

代码示例

import UIKit

// 先定义状态模型,对应API返回的结构
struct TrackingStatus {
    let title: String       // 节点文字,比如"已下单"
    let isCompleted: Bool   // 是否已完成
    let isCurrent: Bool     // 是否是当前节点
}

class TrackingStatusCell: UITableViewCell {
    // 容器视图,用来装所有节点和线
    private let containerView = UIView()
    
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        setupBaseUI()
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        setupBaseUI()
    }
    
    // 初始化基础布局
    private func setupBaseUI() {
        contentView.addSubview(containerView)
        containerView.translatesAutoresizingMaskIntoConstraints = false
        
        // 给容器视图加约束,和cell保持边距
        NSLayoutConstraint.activate([
            containerView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 16),
            containerView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16),
            containerView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16),
            containerView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -16)
        ])
    }
    
    // 核心配置方法,传入状态数组
    func configure(with statuses: [TrackingStatus]) {
        // 先清空之前的子视图,避免cell复用重复添加
        containerView.subviews.forEach { $0.removeFromSuperview() }
        
        var previousCircle: UIView?
        for (index, status) in statuses.enumerated() {
            // 1. 创建节点圆
            let circle = UIView()
            circle.translatesAutoresizingMaskIntoConstraints = false
            circle.layer.cornerRadius = 10 // 圆的半径,按需调整
            // 根据状态设置颜色
            circle.backgroundColor = status.isCompleted ? .systemGreen : status.isCurrent ? .systemBlue : .lightGray
            containerView.addSubview(circle)
            
            // 布局圆的垂直居中,固定大小
            NSLayoutConstraint.activate([
                circle.centerYAnchor.constraint(equalTo: containerView.centerYAnchor),
                circle.widthAnchor.constraint(equalToConstant: 20),
                circle.heightAnchor.constraint(equalToConstant: 20)
            ])
            
            // 2. 处理水平布局和连接线
            if index == 0 {
                // 第一个圆左对齐容器
                circle.leadingAnchor.constraint(equalTo: containerView.leadingAnchor).isActive = true
            } else {
                guard let prevCircle = previousCircle else { continue }
                // 后续圆和前一个圆保持固定间距
                circle.leadingAnchor.constraint(equalTo: prevCircle.trailingAnchor, constant: 40).isActive = true
                
                // 添加连接线
                let line = UIView()
                line.translatesAutoresizingMaskIntoConstraints = false
                line.backgroundColor = .lightGray
                containerView.addSubview(line)
                
                NSLayoutConstraint.activate([
                    line.leadingAnchor.constraint(equalTo: prevCircle.trailingAnchor),
                    line.trailingAnchor.constraint(equalTo: circle.leadingAnchor),
                    line.centerYAnchor.constraint(equalTo: containerView.centerYAnchor),
                    line.heightAnchor.constraint(equalToConstant: 1)
                ])
            }
            
            // 3. 添加状态文字(可选,按需添加)
            let statusLabel = UILabel()
            statusLabel.translatesAutoresizingMaskIntoConstraints = false
            statusLabel.text = status.title
            statusLabel.font = UIFont.systemFont(ofSize: 12)
            statusLabel.textColor = status.isCompleted ? .label : .secondaryLabel
            statusLabel.textAlignment = .center
            containerView.addSubview(statusLabel)
            
            NSLayoutConstraint.activate([
                statusLabel.topAnchor.constraint(equalTo: circle.bottomAnchor, constant: 8),
                statusLabel.centerXAnchor.constraint(equalTo: circle.centerXAnchor)
            ])
            
            previousCircle = circle
        }
    }
}

方案二:内嵌UICollectionView(适合节点数多、需要横向滚动的场景)

如果节点数量可能超过屏幕宽度,或者需要横向滚动查看,这个方案更合适:把每个节点做成CollectionView的Item,然后把CollectionView内嵌到UITableViewCell里。

代码示例

import UIKit

struct TrackingStatus {
    let title: String
    let isCompleted: Bool
    let isCurrent: Bool
}

// 自定义CollectionViewCell,单个节点的视图
class TrackingNodeCell: UICollectionViewCell {
    private let circleView = UIView()
    private let statusLabel = UILabel()
    private let lineView = UIView()
    
    // 控制是否显示右侧的连接线
    var showLine: Bool = true {
        didSet {
            lineView.isHidden = !showLine
        }
    }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        setupUI()
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        setupUI()
    }
    
    private func setupUI() {
        // 布局节点圆
        circleView.translatesAutoresizingMaskIntoConstraints = false
        circleView.layer.cornerRadius = 10
        contentView.addSubview(circleView)
        
        // 布局状态文字
        statusLabel.translatesAutoresizingMaskIntoConstraints = false
        statusLabel.font = UIFont.systemFont(ofSize: 12)
        statusLabel.textAlignment = .center
        statusLabel.numberOfLines = 0
        contentView.addSubview(statusLabel)
        
        // 布局连接线
        lineView.translatesAutoresizingMaskIntoConstraints = false
        lineView.backgroundColor = .lightGray
        contentView.addSubview(lineView)
        
        NSLayoutConstraint.activate([
            circleView.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
            circleView.topAnchor.constraint(equalTo: contentView.topAnchor),
            circleView.widthAnchor.constraint(equalToConstant: 20),
            circleView.heightAnchor.constraint(equalToConstant: 20),
            
            statusLabel.topAnchor.constraint(equalTo: circleView.bottomAnchor, constant: 8),
            statusLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
            statusLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
            statusLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
            
            lineView.leadingAnchor.constraint(equalTo: circleView.trailingAnchor),
            lineView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
            lineView.centerYAnchor.constraint(equalTo: circleView.centerYAnchor),
            lineView.heightAnchor.constraint(equalToConstant: 1)
        ])
    }
    
    func configure(with status: TrackingStatus) {
        circleView.backgroundColor = status.isCompleted ? .systemGreen : status.isCurrent ? .systemBlue : .lightGray
        statusLabel.text = status.title
        statusLabel.textColor = status.isCompleted ? .label : .secondaryLabel
    }
}

// 内嵌CollectionView的UITableViewCell
class TrackingStatusCell: UITableViewCell, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
    private let collectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout())
    private var statuses: [TrackingStatus] = []
    
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        setupCollectionView()
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        setupCollectionView()
    }
    
    private func setupCollectionView() {
        // 设置横向滚动的FlowLayout
        let layout = UICollectionViewFlowLayout()
        layout.scrollDirection = .horizontal
        layout.minimumInteritemSpacing = 20 // 节点之间的间距
        collectionView.collectionViewLayout = layout
        
        collectionView.translatesAutoresizingMaskIntoConstraints = false
        collectionView.backgroundColor = .clear
        collectionView.showsHorizontalScrollIndicator = false
        collectionView.register(TrackingNodeCell.self, forCellWithReuseIdentifier: "TrackingNodeCell")
        
        collectionView.dataSource = self
        collectionView.delegate = self
        
        contentView.addSubview(collectionView)
        
        // 给CollectionView加约束
        NSLayoutConstraint.activate([
            collectionView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 16),
            collectionView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16),
            collectionView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16),
            collectionView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -16),
            collectionView.heightAnchor.constraint(greaterThanOrEqualToConstant: 60) // 确保足够显示内容
        ])
    }
    
    func configure(with statuses: [TrackingStatus]) {
        self.statuses = statuses
        collectionView.reloadData()
    }
    
    // MARK: - UICollectionView数据源方法
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return statuses.count
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "TrackingNodeCell", for: indexPath) as! TrackingNodeCell
        let status = statuses[indexPath.item]
        cell.configure(with: status)
        // 最后一个节点不显示连接线
        cell.showLine = indexPath.item != statuses.count - 1
        return cell
    }
    
    // MARK: - UICollectionView布局方法
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        // 计算文字宽度,确保item能容纳文字
        let textWidth = statuses[indexPath.item].title.size(withAttributes: [.font: UIFont.systemFont(ofSize: 12)]).width
        // 最小宽度设为40,保证圆的布局空间
        return CGSize(width: max(40, textWidth + 16), height: collectionView.bounds.height)
    }
}

注意事项

  • Cell复用问题:不管用哪个方案,都要在configure方法里重置状态,避免复用之前的视图或数据
  • 样式可配置化:可以把颜色、间距、圆的大小这些做成常量或者可配置的属性,方便后续调整
  • 动画优化:如果需要状态切换的动画,可以在configure方法里给圆的颜色变化加UIView.animate过渡

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.12 04:17:18