我有一个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)
}
}
答案 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()
}
}