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

UICollectionView切换支付方式时重配置崩溃及自定义ContentConfiguration问题

解决UICollectionViewDiffableDataSource切换不同Cell类型时的崩溃与更新问题

问题本质

崩溃的核心原因是:调用snapshot.reconfigureItems()时,系统会复用现有Cell并重新配置,不允许在此过程中切换不同的Cell注册/复用标识符。你的代码在Cell Provider中根据paymentMethod返回不同类型的Cell,触发reconfigure时会出现「原Cell类型与新请求Cell类型不匹配」的冲突,直接导致崩溃。


方案一:通过更新Snapshot替换Item(推荐)

既然需要切换不同Cell类型,不能用reconfigureItems,而是要移除原Item再重新添加,让DiffableDataSource重新创建对应类型的Cell,而非复用旧Cell。

修改代码

  1. 更新paymentMethod的didSet:
var paymentMethod: String = " Pay" {
    didSet {
        var snapshot = dataSource.snapshot()
        // 移除旧Item后重新添加,触发数据源创建新Cell
        snapshot.deleteItems(snapshot.itemIdentifiers)
        snapshot.appendItems(["demo"])
        dataSource.apply(snapshot, animatingDifferences: true)
    }
}
  1. 保持Cell Provider的weak self捕获,确保能拿到最新的paymentMethod:
dataSource = .init(collectionView: collectionView) { [weak self] collectionView, indexPath, itemIdentifier in
    guard let self else { return nil }
    
    return switch self.paymentMethod {
    case " Pay":
        collectionView.dequeueConfiguredReusableCell(using: applePayButtonCellRegistration, for: indexPath, item: itemIdentifier)
    default:
        collectionView.dequeueConfiguredReusableCell(using: payCashAtDeliveryCellRegistration, for: indexPath, item: itemIdentifier)
    }
}

此方案既保证了weak self的内存安全,又能让数据源根据最新的支付方式创建正确的Cell,不会触发崩溃。


方案二:使用单个Cell类型,内部切换视图

如果需要平滑的更新动画,可采用通用Cell,内部根据支付方式显示/隐藏对应按钮,这样就能正常使用reconfigureItems更新内容。

修改代码

  1. 创建通用Cell:
class PaymentButtonCell: UICollectionViewListCell {
    let applePayButton = PKPaymentButton(paymentButtonType: .checkout, paymentButtonStyle: .automatic)
    let cashButton: UIButton = {
        var config = UIButton.Configuration.plain()
        var attr = AttributeContainer()
        attr.font = .preferredFont(forTextStyle: .headline)
        attr.foregroundColor = .systemBlue
        config.attributedTitle = .init("Ordina", attributes: attr)
        return UIButton(configuration: config, primaryAction: nil)
    }()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        [applePayButton, cashButton].forEach {
            $0.translatesAutoresizingMaskIntoConstraints = false
            contentView.addSubview($0)
            NSLayoutConstraint.activate([
                $0.topAnchor.constraint(equalTo: contentView.topAnchor),
                $0.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
                $0.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
                $0.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
                $0.heightAnchor.constraint(greaterThanOrEqualToConstant: 44)
            ])
        }
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    func configure(with paymentMethod: String, target: Any?, action: Selector) {
        applePayButton.isHidden = paymentMethod != " Pay"
        cashButton.isHidden = paymentMethod == " Pay"
        
        // 先移除旧Target避免重复绑定
        applePayButton.removeTarget(target, action: action, for: .touchUpInside)
        cashButton.removeTarget(target, action: action, for: .touchUpInside)
        
        applePayButton.addTarget(target, action: action, for: .touchUpInside)
        cashButton.addTarget(target, action: action, for: .touchUpInside)
    }
}
  1. 修改数据源配置:
func configureDataSource() {
    let cellRegistration = UICollectionView.CellRegistration<PaymentButtonCell, String> { [weak self] cell, indexPath, itemIdentifier in
        guard let self else { return }
        cell.configure(with: self.paymentMethod, target: self, action: #selector(self.checkout))
    }
    
    dataSource = .init(collectionView: collectionView) { collectionView, indexPath, itemIdentifier in
        collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: itemIdentifier)
    }
    
    var snapshot = NSDiffableDataSourceSnapshot<String, String>()
    snapshot.appendSections(["main"])
    snapshot.appendItems(["demo"])
    dataSource.apply(snapshot, animatingDifferences: false)
}
  1. 保持paymentMethod的didSet逻辑不变:
var paymentMethod: String = " Pay" {
    didSet {
        var snapshot = dataSource.snapshot()
        snapshot.reconfigureItems(snapshot.itemIdentifiers)
        dataSource.apply(snapshot, animatingDifferences: true)
    }
}

此方案避免了Cell类型切换,适合需要平滑过渡动画的场景,且不会触发性能警告。


之前尝试的问题解析

  • 捕获[paymentMethod]是值捕获,只会拿到configureDataSource执行时的初始值,后续paymentMethod更新时,闭包内仍为旧值,导致Cell不更新。
  • reconfigureItems的设计目的是同一Cell类型下的内容更新,切换Cell类型违反了DiffableDataSource的规则,必然触发崩溃。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 17:14:55