如何在单个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
相关产品推荐
相关产品推荐

