UITableView中sendSubviewToBack失效,如何调整单元格层级?
我想要调整UITableView的视图层级,已经使用sendSubviewToBack和bringSubviewToFront方法,且先创建RedCell,需要确保RedCell位于BlueCell下方(RedCell中的overBgView不应遮挡BlueCell),但实际结果不符合预期:RedCell的黄色overBgView覆盖在了BlueCell上方。
相关代码
import UIKit class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return 2 } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { print("<<< \(indexPath)") if indexPath.row == 0 { let cell = tableView.dequeueReusableCell(withIdentifier: RedCell.identifier, for: indexPath) as! RedCell // RedCell tableView.sendSubviewToBack(cell) return cell } else { let cell = tableView.dequeueReusableCell(withIdentifier: BlueCell.identifier, for: indexPath) as! BlueCell // BlueCell tableView.bringSubviewToFront(cell) return cell } } private lazy var tableView: UITableView = { let table = UITableView(frame: .zero, style: .plain) table.estimatedRowHeight = 80 table.showsVerticalScrollIndicator = false table.separatorStyle = .none table.delegate = self table.dataSource = self table.register(RedCell.self, forCellReuseIdentifier: RedCell.identifier) table.register(BlueCell.self, forCellReuseIdentifier: BlueCell.identifier) return table }() private lazy var label: UILabel = { let label = UILabel() label.text = "------split----the views below are not table cell" label.textColor = .black return label }() private lazy var view1: UIViewOne = { let view = UIViewOne() return view }() private lazy var view2: UIView = { let view = UIView() view.backgroundColor = .blue return view }() override func viewDidLoad() { super.viewDidLoad() tableView.register(RedCell.self, forCellReuseIdentifier: "redCell") tableView.register(BlueCell.self, forCellReuseIdentifier: "blueCell") setupUI() } public init() { super.init(nibName: nil, bundle: nil) } required init?(coder: NSCoder) { super.init(coder: coder) } private func setupUI() { view.addSubview(tableView) view.addSubview(label) view.addSubview(view1) view.addSubview(view2) tableView.snp.makeConstraints { make in make.top.equalToSuperview().inset(200) make.left.right.equalToSuperview() make.height.equalTo(120) } label.snp.makeConstraints { make in make.top.equalTo(tableView.snp.bottom).offset(30) make.centerX.equalToSuperview() make.height.equalTo(20) } view1.snp.makeConstraints { make in make.top.equalTo(label.snp.bottom).offset(30) make.left.right.equalToSuperview() make.height.equalTo(44) } view2.snp.makeConstraints { make in make.top.equalTo(view1.snp.bottom) make.left.right.equalToSuperview() make.height.equalTo(44) } } } class RedCell: UITableViewCell { static let identifier = "RedCell" private lazy var overBgView: UIView = { let view = UIView() view.backgroundColor = .yellow return view }() override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) backgroundColor = .red setupUI() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } private func setupUI() { contentView.addSubview(overBgView) // contentView.sendSubviewToBack(overBgView) self.superview?.sendSubviewToBack(overBgView) overBgView.snp.makeConstraints { make in make.top.left.right.equalToSuperview() make.height.equalTo(60) } } } class BlueCell: UITableViewCell { static let identifier = "BlueCell" override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) backgroundColor = .blue setupUI() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } private func setupUI() { } } class UIViewOne: UIView { private lazy var oversizeView: UIView = { let view = UIView() view.backgroundColor = .yellow return view }() override init(frame: CGRect) { super.init(frame: frame) backgroundColor = .red addSubview(oversizeView) oversizeView.snp.makeConstraints { make in make.top.left.right.equalToSuperview() make.height.equalTo(60) } } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } }
核心问题1:Cell初始化时操作父视图层级无效
在RedCell的setupUI方法中,self.superview?.sendSubviewToBack(overBgView)完全无效——Cell初始化阶段还未被添加到UITableView,此时superview为nil,代码不会执行。而且overBgView是添加在contentView上的,你却试图通过Cell的父视图调整它的层级,逻辑错误。
核心问题2:UITableView的Cell层级由系统自动管理,手动调整会被覆盖
UITableView会根据滚动、复用逻辑自动维护Cell的层级关系,在cellForRowAt中调用sendSubviewToBack/bringSubviewToFront的操作会被系统后续的布局逻辑覆盖,无法保证持久生效。
修复步骤
修正RedCell中overBgView的层级
将overBgView放在contentView的最底层,直接调用contentView的层级调整方法即可:private func setupUI() { contentView.addSubview(overBgView) contentView.sendSubviewToBack(overBgView) // 替换原无效代码 overBgView.snp.makeConstraints { make in make.top.left.right.equalToSuperview() make.height.equalTo(60) } }通过layer.zPosition强制指定Cell层级
利用图层的zPosition属性控制Cell的显示层级,数值越大越靠上,这个设置不会被UITableView的系统逻辑覆盖:
修改cellForRowAt方法:func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { print("<<< \(indexPath)") if indexPath.row == 0 { let cell = tableView.dequeueReusableCell(withIdentifier: RedCell.identifier, for: indexPath) as! RedCell cell.layer.zPosition = 0 // RedCell层级更低 return cell } else { let cell = tableView.dequeueReusableCell(withIdentifier: BlueCell.identifier, for: indexPath) as! BlueCell cell.layer.zPosition = 1 // BlueCell层级更高 return cell } }移除重复的Cell注册代码
viewDidLoad中重复注册Cell的代码无意义,直接删除:// 移除以下两行 // tableView.register(RedCell.self, forCellReuseIdentifier: "redCell") // tableView.register(BlueCell.self, forCellReuseIdentifier: "blueCell")因为在tableView的懒加载初始化代码中已经完成了Cell注册。
最终效果
修复后,BlueCell会稳定显示在RedCell上方,RedCell的黄色overBgView不会遮挡BlueCell,符合预期需求。
内容的提问来源于stack exchange,提问作者malcopolocei

