CollectionView referenceSizeForHeaderInSection是应用程序崩溃的原因

时间:2018-01-05 10:04:41

标签: ios swift uicollectionview ios11 crash

我有一个CollectionView,有两个部分。第一部分包含新匹配(尚未激活),第二部分包含活动匹配。我有不同状态匹配的观察者(新的,更新或删除)。删除或添加新单元格时,通常会发生应用程序崩溃。错误消息Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[__NSDictionaryM setObject:forKey:]: key cannot be nil。我读过post这样做了,但这对我没有帮助。我试图删除referenceSizeForHeaderInSection方法,而且我没有让应用程序崩溃。请告诉我如何解决这个问题,因为我需要这个方法,如果该部分中没有数据,那么应该隐藏部分标题。

此控制器的完整代码。

class MatchListViewController: UIViewController {

    // MARK: - UI

    private lazy var collectionView: UICollectionView = {
        let layout = UICollectionViewFlowLayout()
        layout.scrollDirection = .vertical
        layout.sectionInset = UIEdgeInsets(top: 0, left: 8, bottom: 0, right: 8)
        layout.minimumLineSpacing = 8
        layout.minimumInteritemSpacing = 8
        layout.headerReferenceSize = CGSize(width: UIScreen.main.bounds.width, height: 30)
        let collection = UICollectionView(frame: .zero, collectionViewLayout: layout)
        collection.translatesAutoresizingMaskIntoConstraints = false
        collection.backgroundColor = .white
        return collection
    }()

    // MARK: - Constants

    private let cellSize = { () -> CGFloat in
        let screenSize = UIScreen.main.bounds
        let screenWidth = screenSize.width
        let collectionEdgesInsets: CGFloat = 12.0

        return screenWidth/2 -  collectionEdgesInsets - collectionEdgesInsets/2
    }()

    // MARK: - Another controller

    private var parentPager: PeoplePagerViewController!

    // MARK: - Init

    init(_ parentPager: PeoplePagerViewController) {
        self.parentPager = parentPager
        super.init(nibName: nil, bundle: nil)
    }

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

    // MARK: - Flags

    // MARK: - Managers
    private let matchingDownloaderManager = MatchDownloaderManager()
    private let constraintManager = ConstraintManager()

    // MARK: - Data
    private var newInactiveMatches = [MatchPersonalModel]()
    private var activeMatches = [MatchPersonalModel]()

    // MARK: - Lifecycle

    override func viewDidLoad() {
        super.viewDidLoad()
        setupSettings()
        setupUISettings()
        // ui
        addUIElements()
        // collection view
        setupCollectionViewSettings()
        // request
        requestMatches()
        // observers
        addObservers()
    }

    deinit {
        removeObservers()
    }

    // MARK: - Override functions

    override func viewWillLayoutSubviews() {
        super.viewWillLayoutSubviews()
        setupUIElementsSafeAreaPositions()
    }

    // MARK: - Settings

    private func setupSettings() {
        definesPresentationContext = true
    }

    private func setupUISettings() {
        view.backgroundColor = .white
    }

    // MARK: - UI Actions

    private func addUIElements() {
        view.addSubview(collectionView)
    }

    private func setupUIElementsSafeAreaPositions() {
        collectionView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
        collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
        collectionView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
        collectionView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
    }

}

// MARK: - Nav bar

extension MatchListViewController {

    func removeNavBarItems() {
        parentPager.navigationItem.title = nil
        parentPager.navigationItem.rightBarButtonItem = nil
    }

}

// MARK: - Collection view

// MARK: - Collection View Data Source and delegate

extension MatchListViewController: UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {

    private func setupCollectionViewSettings() {
        collectionView.dataSource = self
        collectionView.delegate = self
        addRefreshControl()
        registerCells()
    }

    private func addRefreshControl() {
        let refreshControl = UIRefreshControl()
        refreshControl.addTarget(self, action: #selector(requestMatches), for: .valueChanged)
        collectionView.refreshControl = refreshControl
    }

    private func registerCells() {
        let matchListCellNib = UINib(nibName: MatchListCollectionViewCell.defaultReuseIdentifier, bundle: Bundle.main)
        collectionView.register(matchListCellNib, forCellWithReuseIdentifier: MatchListCollectionViewCell.defaultReuseIdentifier)
        collectionView.register(MatchCollectionReusableView.self, forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, withReuseIdentifier: "MatchCollectionReusableView")
    }

    func numberOfSections(in collectionView: UICollectionView) -> Int {
        return 2
    }

    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        if section == 0 {
            return newInactiveMatches.count
        } else if section == 1 {
            return activeMatches.count
        } else {
            return 0
        }
    }

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = matchListCell(collectionView, indexPath: indexPath)
        return cell
    }

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        let size = CGSize(width: cellSize, height: constraintManager.transformedValue4_7(264.5, axis: .Height))
        return size
    }

    func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
        switch kind {
        case UICollectionElementKindSectionHeader:
            let header = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionElementKindSectionHeader, withReuseIdentifier: "MatchCollectionReusableView", for: indexPath) as! MatchCollectionReusableView
            if indexPath.section == 0 {
                header.populate("New Matches")
            } else if indexPath.section == 1 {
                header.populate("Matches")
            }
            return header
        default: return UICollectionReusableView()
        }
    }

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
        if section == 0 && newInactiveMatches.count > 0 {
            return CGSize(width: UIScreen.main.bounds.width, height: 30)
        } else if section == 1 && activeMatches.count > 0  {
            return CGSize(width: UIScreen.main.bounds.width, height: 30)
        }
        return CGSize.zero
    }

    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        var match: MatchPersonalModel!
        if indexPath.section == 0 {
            match = newInactiveMatches[indexPath.row]
        } else if indexPath.section == 1 {
            match = activeMatches[indexPath.row]
        }
        collectionView.deselectItem(at: indexPath, animated: true)
        guard let _match = match else { return }
        pushProfileUserFormVC(_match)
    }


}

// MARK: - Collection View Cell

extension MatchListViewController {

    private func matchListCell(_ collectionView: UICollectionView, indexPath: IndexPath) -> MatchListCollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: MatchListCollectionViewCell.defaultReuseIdentifier, for: indexPath) as! MatchListCollectionViewCell
        var match: MatchPersonalModel!
        if indexPath.section == 0 {
            match = newInactiveMatches[indexPath.row]
        } else if indexPath.section == 1 {
            match = activeMatches[indexPath.row]
        }

        if let _match = match {
            cell.matchPopulate(_match)
        }

        return cell
    }

}


// MARK: - Requests

extension MatchListViewController {

    @objc private func requestMatches() {
        matchingDownloaderManager.downloadForMatchVC(success: { [weak self] (newMatches, activeMatches) in
            DispatchQueue.main.async {
                self?.newInactiveMatches = newMatches
                self?.activeMatches = activeMatches
                self?.collectionView.refreshControl?.endRefreshing()
                self?.collectionView.reloadData()
            }
        }) { (error) in
            DispatchQueue.main.async {
                HUD.showError(error.localizedDescription)
            }
        }
    }

}

// MARK: - Observers

extension MatchListViewController {

    private func addObservers() {
        MatchObserverManager.shared.observeNewMatches(self, isObserve: true, completion: { [weak self] (match) in
            DispatchQueue.main.async {
                self?.collectionView.performBatchUpdates({
                    self?.collectionView.reloadData()
                    self?.collectionView.numberOfItems(inSection: 0)
                    self?.newInactiveMatches.insert(match, at: 0)
                    let indexPath = IndexPath(row: 0, section: 0)
                    self?.collectionView.insertItems(at: [indexPath])
                    self?.collectionView.reloadData()
                }, completion: nil)
            }
        }) { (error) in
            DispatchQueue.main.async {
                guard !HUD.isVisible() else { return }
                HUD.showError(error.localizedDescription)
            }
        }

        MatchObserverManager.shared.observeExistMatches(self, isObserve: true, completion: { [weak self] (match) in
            DispatchQueue.main.async {
                self?.manageExistMatch(match)
            }
        }) { (error) in
            DispatchQueue.main.async {
                guard !HUD.isVisible() else { return }
                HUD.showError(error.localizedDescription)
            }
        }

        MatchObserverManager.shared.observeMatchDelete(self, isObserve: true, completion: { [weak self] (deletedMatch) in
            DispatchQueue.main.async {
                self?.deleteMatch(deletedMatch)
            }
        }) { (error) in
            DispatchQueue.main.async {
                guard !HUD.isVisible() else { return }
                HUD.showError(error.localizedDescription)
            }
        }
    }

    private func removeObservers() {
        MatchObserverManager.shared.observeNewMatches(self, isObserve: false, completion: nil, fail: nil)
        MatchObserverManager.shared.observeExistMatches(self, isObserve: false, completion: nil, fail: nil)
        MatchObserverManager.shared.observeMatchDelete(self, isObserve: false, completion: nil, fail: nil)
    }

}

// MARK: - Matches managing

extension MatchListViewController {

    private func manageExistMatch(_ match: MatchPersonalModel) {
        if let index = newInactiveMatches.index(where: { $0.matchID == match.matchID }), match.isActive {
            collectionView.performBatchUpdates({
                newInactiveMatches.remove(at: index)
                let indexPath = IndexPath(row: index, section: 0)
                collectionView.deleteItems(at: [indexPath])
                activeMatches.insert(match, at: 0)
                let activeIndexPath = IndexPath(row: 0, section: 1)
                collectionView.insertItems(at: [activeIndexPath])
            }, completion: { [weak self] (isCommited) in
                self?.collectionView.reloadData() // **App crash here**
            })
            return
        }

        guard let activeIndex = activeMatches.index(where: { $0.matchID == match.matchID }) else { return }
        activeMatches[activeIndex] = match
        let indexPath = IndexPath(row: activeIndex, section: 1)
        collectionView.performBatchUpdates({
            collectionView.numberOfItems(inSection: 0)
            collectionView.reloadItems(at: [indexPath])
        }, completion: nil)
    }

    private func deleteMatch(_ match: MatchPersonalModel) {
        if let newMatchIndex = newInactiveMatches.index(where: { $0.matchID == match.matchID}) {
            deleteMatch(newMatchIndex, isNewMatch: true)
            return
        }

        if let activeMatchIndex = activeMatches.index(where: { $0.matchID == match.matchID }) {
            deleteMatch(activeMatchIndex, isNewMatch: false)
            return
        }
    }

    private func deleteMatch(_ index: Int, isNewMatch: Bool) {
        var sectionIndex = 1 // by default active match
        if isNewMatch {
            sectionIndex = 0
        }

        let indexPath = IndexPath(row: index, section: sectionIndex)
        if isNewMatch {
            newInactiveMatches.remove(at: index)
        } else {
            activeMatches.remove(at: index)
        }

        collectionView.performBatchUpdates({
            collectionView.deleteItems(at: [indexPath])
        }) { [weak self] (isCompleted) in
            self?.collectionView.reloadData() **// App crash here**
        }
    }

}

// MARK: - Navigation

extension MatchListViewController {

    private func pushProfileUserFormVC(_ match: MatchPersonalModel) {
        let profileUserFormVC = ProfileUserFormVC(userID: match.userInfo.userID, parentPager: parentPager)
        navigationController?.pushViewController(profileUserFormVC, animated: true)
    }


}

1 个答案:

答案 0 :(得分:1)

我读了这个issue虽然我没有使用这个框架,但这个collectionView.collectionViewLayout.invalidateLayout()方法帮助了我

我现在的功能示例

private func deleteMatch(_ index: Int, isNewMatch: Bool) {
    var sectionIndex = 1 // by default active match
    if isNewMatch {
        sectionIndex = 0
    }

    let indexPath = IndexPath(row: index, section: sectionIndex)
    if isNewMatch {
        newInactiveMatches.remove(at: index)
    } else {
        activeMatches.remove(at: index)
    }

    collectionView.performBatchUpdates({
        collectionView.deleteItems(at: [indexPath])
        debugPrint("deleted items")
        collectionView.collectionViewLayout.invalidateLayout()
    }) { [weak self] (isCompleted) in
        self?.collectionView.reloadData()
    }
}