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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 13:50:28