RxSwiftCommunity / RxDataSources

UITableView and UICollectionView Data Sources for RxSwift (sections, animated updates, editing ...)
MIT License
3.1k stars 496 forks source link

RxCollectionViewSectionedAnimatedDataSource crash if estimatedItemSize was set in iOS 14.0&14.0.1 #416

Open izhoubin opened 2 years ago

izhoubin commented 2 years ago

I have a problem here,and need your help.

It will always crash If the conditions above are met, in file 'RxCollectionViewSectionedAnimatedDataSource' at collectionView.performBatchUpdates(updateBlock, completion: nil) in both simulators and phones.

image

Xcode :Version 13.4 (13F17a) System: macOS 12.3.1 (21E258)

You can reproduce by the following code and click Reload:

import UIKit
import RxDataSources
import RxCocoa
import RxSwift
class ViewController: UIViewController {
    var collectionView:UICollectionView!
    var dataSouce:RxCollectionViewSectionedAnimatedDataSource<AnimatableSectionModel<Int,String>>!
    let items:BehaviorRelay<[String]> = BehaviorRelay(value:["1","2","3","4","5","6"])
    let disposeBag:DisposeBag = DisposeBag()
    override func viewDidLoad() {
        super.viewDidLoad()
        setupCollectionView()
        setupReload()
        setupRx()
    }
    func setupReload(){
        let reloadButton = UIButton()
        reloadButton.setTitle("Click here to reload", for: UIControl.State.normal)
        reloadButton.setTitleColor(UIColor.black, for: UIControl.State.normal)
        view.addSubview(reloadButton)
        reloadButton.frame.size = CGSize(width: 200, height: 50)
        reloadButton.center = view.center
        reloadButton.addTarget(self, action: #selector(reload), for: UIControl.Event.touchUpInside)
    }
    @objc func reload(){
        items.accept(items.value.shuffled())
    }

    func setupCollectionView(){
        let layout = UICollectionViewFlowLayout()
        //crash when reload if 'estimatedItemSize' is set in iOS 14.0 & 14.0.1
        layout.estimatedItemSize = CGSize(width: 80, height: 50)
        layout.minimumLineSpacing = 10
        layout.minimumInteritemSpacing = 10
        collectionView = UICollectionView(frame: UIScreen.main.bounds, collectionViewLayout: layout)
        collectionView.backgroundColor = UIColor.white
        view.addSubview(collectionView)

        collectionView.register(CollectionViewCell.self, forCellWithReuseIdentifier: "CollectionViewCell")
    }

    func setupRx(){
        dataSouce = RxCollectionViewSectionedAnimatedDataSource<AnimatableSectionModel<Int,String>>(configureCell: {(dt, collectionView, indexPath, model) -> UICollectionViewCell in
            let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CollectionViewCell", for: indexPath) as! CollectionViewCell
            cell.backgroundColor = UIColor.blue
            cell.label.text = model
            return cell
        })
        items
            .map{[AnimatableSectionModel(model: 0, items: $0)]}
            .bind(to:collectionView.rx.items(dataSource: dataSouce))
            .disposed(by:disposeBag)

    }
}
class CollectionViewCell: UICollectionViewCell {
    let label = UILabel()
    override init(frame: CGRect) {
        super.init(frame: frame)
        contentView.addSubview(label)
        label.textAlignment = .center
        label.font = UIFont.systemFont(ofSize: 30)
        label.backgroundColor = UIColor.yellow
        contentView.backgroundColor = UIColor.purple
        label.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint(item: label, attribute: .left, relatedBy: .equal, toItem: contentView, attribute: .left, multiplier: 1, constant: 10).isActive = true
        NSLayoutConstraint(item: label, attribute: .right, relatedBy: .equal, toItem: contentView, attribute: .right, multiplier: 1, constant: -10).isActive = true
        NSLayoutConstraint(item: label, attribute: .top, relatedBy: .equal, toItem: contentView, attribute: .top, multiplier: 1, constant: 10).isActive = true
        NSLayoutConstraint(item: label, attribute: .bottom, relatedBy: .equal, toItem: contentView, attribute: .bottom, multiplier: 1, constant: -10).isActive = true
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}