UIKit中用NotificationCenter实现颜色切换时界面冻结问题求助
UIKit中使用NotificationCenter切换颜色后界面冻结的解决方案
问题根源
界面冻结的核心原因是违反了UITableView的单元格复用机制:在AccentColorVC中,你直接返回预创建的AccentColorTVCell实例,而非通过dequeueReusableCell方法获取可复用单元格。这种操作会导致表格视图内部视图层级混乱、复用池失效,最终引发主线程阻塞和界面卡死。
修复步骤
1. 修复AccentColorVC的单元格复用逻辑
将预创建单元格数组改为存储颜色数据,通过表格视图的复用机制创建和配置单元格:
class AccentColorVC: UIViewController { let tableView = UITableView() let identifier = "AccentColorCell" // 存储颜色数据,而非预创建单元格 let colorData: [(color: UIColor, label: String)] = [ (.systemRed, "Red"), (.systemGreen, "Green"), (.systemBlue, "Blue") ] var selectedIndex: Int? // 记录选中的颜色索引 override func viewDidLoad() { super.viewDidLoad() configureViewController() configureTableView() } private func configureViewController() { title = "Accent Color" view.backgroundColor = .systemBackground } private func configureTableView() { view.addSubview(tableView) tableView.rowHeight = 80 tableView.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ tableView.topAnchor.constraint(equalTo: view.topAnchor), tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor), tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor), tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor) ]) tableView.delegate = self tableView.dataSource = self // 注册自定义单元格(而非UITableViewCell) tableView.register(AccentColorTVCell.self, forCellReuseIdentifier: identifier) } } extension AccentColorVC: UITableViewDelegate, UITableViewDataSource { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return colorData.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { // 复用自定义单元格 let cell = tableView.dequeueReusableCell(withIdentifier: identifier, for: indexPath) as! AccentColorTVCell let data = colorData[indexPath.row] // 配置单元格内容 cell.color = data.color cell.label = data.label cell.isChosen = (selectedIndex == indexPath.row) return cell } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) selectedIndex = indexPath.row tableView.reloadRows(at: [indexPath], with: .automatic) let selectedColor = colorData[indexPath.row].color NotificationCenter.default.post(name: .colorDidChange, object: nil, userInfo: ["color": selectedColor]) } }
2. 优化AccentColorTVCell的属性更新逻辑
为单元格属性添加didSet监听,自动同步UI变化:
class AccentColorTVCell: UITableViewCell { let colorIcon = UIView() let colorLabel = UILabel() let colorSelected = UIImageView(image: UIImage(systemName: "checkmark.circle.fill")) var color: UIColor = .clear { didSet { colorIcon.backgroundColor = color } } var label: String = "" { didSet { colorLabel.text = label } } var isChosen: Bool = false { didSet { colorSelected.tintColor = isChosen ? .systemBlue : .clear } } override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) configureCell() } init(color: UIColor, label: String, isChosen: Bool) { self.color = color self.label = label self.isChosen = isChosen super.init(style: .default, reuseIdentifier: nil) configureCell() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } private func configureCell() { addSubview(colorIcon) addSubview(colorLabel) addSubview(colorSelected) colorIcon.layer.cornerRadius = 12 colorLabel.font = UIFont.systemFont(ofSize: 24, weight: .bold) let padding: CGFloat = 20 colorIcon.translatesAutoresizingMaskIntoConstraints = false colorLabel.translatesAutoresizingMaskIntoConstraints = false colorSelected.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ colorIcon.centerYAnchor.constraint(equalTo: self.centerYAnchor), colorIcon.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: padding), colorIcon.heightAnchor.constraint(equalToConstant: 60), colorIcon.widthAnchor.constraint(equalToConstant: 60), colorLabel.centerYAnchor.constraint(equalTo: self.centerYAnchor), colorLabel.leadingAnchor.constraint(equalTo: colorIcon.trailingAnchor, constant: 24), colorLabel.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -padding), colorLabel.heightAnchor.constraint(equalToConstant: 40), colorSelected.centerYAnchor.constraint(equalTo: self.centerYAnchor), colorSelected.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -padding), colorSelected.heightAnchor.constraint(equalToConstant: 24), colorSelected.widthAnchor.constraint(equalToConstant: 24) ]) } }
3. 确保SettingsVC在主线程更新UI
虽然通知默认在发送线程执行,但显式指定主线程更新UI可避免潜在的线程问题:
@objc func didReceiveColorChangeNotification(_ notification: Notification) { guard let color = notification.userInfo?["color"] as? UIColor else { return } DispatchQueue.main.async { [weak self] in self?.selectedColor = color self?.tableView.reloadData() } }
可选优化:改用闭包替代NotificationCenter
对于页面间的直接通信,闭包比全局通知更清晰,可避免耦合:
在AccentColorVC中添加闭包属性:
var colorSelectedHandler: ((UIColor) -> Void)?
在didSelectRowAt中调用闭包:
colorSelectedHandler?(selectedColor)
在SettingsVC跳转时设置闭包:
let accentVC = AccentColorVC() accentVC.colorSelectedHandler = { [weak self] color in self?.selectedColor = color self?.tableView.reloadData() } navigationController?.pushViewController(accentVC, animated: true)
这样就可以移除NotificationCenter的相关代码,简化逻辑。
内容的提问来源于stack exchange,提问作者MohAliBou
相关产品推荐
相关产品推荐

