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

如何在单个UITableView中混用Plain与Inset Grouped样式单元格

实现方案

因为UITableView的样式是初始化时确定的,无法动态修改,所以我们可以在Plain样式的表视图中模拟Inset Grouped的视觉效果,通过分区划分+自定义单元格布局实现,无需额外添加表视图。

1. 划分表视图分区

把原有单分区拆分为两个逻辑分区,分别对应两种样式的单元格:

  • 分区0:放置模拟Inset Grouped样式的单元格(行数按需设置)
  • 分区1:保留原来的动态行数单元格

示例代码:

enum TableSection: Int, CaseIterable {
    case insetGroupedSection
    case plainSection
}

override func numberOfSections(in tableView: UITableView) -> Int {
    return TableSection.allCases.count
}

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    guard let tableSection = TableSection(rawValue: section) else { return 0 }
    switch tableSection {
    case .insetGroupedSection:
        return 3 // 替换为你需要的Inset Grouped样式行数
    case .plainSection:
        return yourDynamicRowCount // 原动态行数变量
    }
}

2. 模拟Inset Grouped视觉样式

Inset Grouped的核心特征是:分组内边距、带圆角的背景、分组间距。我们通过以下方式实现:

(1)设置分区内边距

override func tableView(_ tableView: UITableView, insetForSectionAt section: Int) -> UIEdgeInsets {
    guard let tableSection = TableSection(rawValue: section) else { return .zero }
    switch tableSection {
    case .insetGroupedSection:
        return UIEdgeInsets(top: 16, left: 16, bottom: 16, right: 16) // 模拟分组间距
    case .plainSection:
        return .zero // 保留原Plain样式内边距
    }
}

(2)自定义单元格布局

创建自定义单元格InsetGroupedCell,或者在cellForRowAt中动态调整样式:

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    guard let tableSection = TableSection(rawValue: indexPath.section) else { return UITableViewCell() }
    
    switch tableSection {
    case .insetGroupedSection:
        let cell = tableView.dequeueReusableCell(withIdentifier: "InsetGroupedCell", for: indexPath) as! InsetGroupedCell
        cell.titleLabel.text = "Inset Grouped Row \(indexPath.row + 1)"
        
        // 处理圆角:首行顶部圆角,末行底部圆角,中间行无额外圆角
        let totalRows = tableView.numberOfRows(inSection: indexPath.section)
        var corners: UIRectCorner = []
        if indexPath.row == 0 { corners.insert([.topLeft, .topRight]) }
        if indexPath.row == totalRows - 1 { corners.insert([.bottomLeft, .bottomRight]) }
        
        cell.setCustomBackground(corners: corners, radius: 12)
        // 隐藏末行分隔线
        cell.separatorLine.isHidden = (indexPath.row == totalRows - 1)
        return cell
        
    case .plainSection:
        let cell = tableView.dequeueReusableCell(withIdentifier: "PlainCell", for: indexPath) as! PlainCell
        cell.textLabel?.text = "Plain Row \(indexPath.row + 1)"
        return cell
    }
}

自定义单元格InsetGroupedCell实现:

class InsetGroupedCell: UITableViewCell {
    let titleLabel = UILabel()
    let separatorLine = UIView()
    private let backgroundContainer = UIView()
    private var targetCorners: UIRectCorner = .allCorners
    private var cornerRadius: CGFloat = 12
    
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        setupLayout()
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        setupLayout()
    }
    
    private func setupLayout() {
        // 隐藏系统背景,使用自定义容器
        backgroundColor = .clear
        backgroundView = nil
        selectedBackgroundView = nil
        
        // 添加背景容器
        contentView.addSubview(backgroundContainer)
        backgroundContainer.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            backgroundContainer.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 4),
            backgroundContainer.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 8),
            backgroundContainer.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -8),
            backgroundContainer.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -4)
        ])
        backgroundContainer.backgroundColor = .systemBackground
        backgroundContainer.layer.masksToBounds = true
        
        // 添加标题标签
        contentView.addSubview(titleLabel)
        titleLabel.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            titleLabel.topAnchor.constraint(equalTo: backgroundContainer.topAnchor, constant: 16),
            titleLabel.leadingAnchor.constraint(equalTo: backgroundContainer.leadingAnchor, constant: 16),
            titleLabel.trailingAnchor.constraint(equalTo: backgroundContainer.trailingAnchor, constant: -16),
            titleLabel.bottomAnchor.constraint(equalTo: backgroundContainer.bottomAnchor, constant: -16)
        ])
        
        // 添加自定义分隔线
        separatorLine.backgroundColor = .separator
        contentView.addSubview(separatorLine)
        separatorLine.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            separatorLine.leadingAnchor.constraint(equalTo: backgroundContainer.leadingAnchor, constant: 16),
            separatorLine.trailingAnchor.constraint(equalTo: backgroundContainer.trailingAnchor),
            separatorLine.bottomAnchor.constraint(equalTo: backgroundContainer.bottomAnchor),
            separatorLine.heightAnchor.constraint(equalToConstant: 0.5)
        ])
    }
    
    func setCustomBackground(corners: UIRectCorner, radius: CGFloat) {
        targetCorners = corners
        cornerRadius = radius
        updateBackgroundMask()
    }
    
    private func updateBackgroundMask() {
        let path = UIBezierPath(roundedRect: backgroundContainer.bounds, byRoundingCorners: targetCorners, cornerRadii: CGSize(width: cornerRadius, height: cornerRadius))
        let mask = CAShapeLayer()
        mask.path = path.cgPath
        backgroundContainer.layer.mask = mask
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        updateBackgroundMask()
    }
    
    // 自定义选中效果
    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)
        backgroundContainer.backgroundColor = selected ? .systemGray5 : .systemBackground
    }
}

3. 功能逻辑处理

所有单元格的点击、数据绑定逻辑和平常一致,在didSelectRowAt中按分区区分处理即可:

override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    tableView.deselectRow(at: indexPath, animated: true)
    guard let tableSection = TableSection(rawValue: indexPath.section) else { return }
    switch tableSection {
    case .insetGroupedSection:
        print("处理Inset Grouped行点击:\(indexPath.row)")
    case .plainSection:
        print("处理Plain行点击:\(indexPath.row)")
    }
}

这种方案在单个表视图内实现了两种样式的单元格布局,比叠加表视图更简洁易维护,视觉效果和原生Inset Grouped几乎一致。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 14:20:34