Reputation: 759
I'm experiencing a problem when rendering removable sections in my collection views. The setup is as follows:
The collection view has two sections. The first one contains only one cell, which when tapped triggers a snapshot update that removes the sections from the collection view. The second section contains several cells scrolling orthogonally.
The problem occurs when scrolling the second section, and then tapping the only cell on the first one.
The behavior I expect is that the first section would be removed, and the section would slide into the first's position in a smooth animation.
What I'm seeing is the orthogonally scrolling section flickering and jumping until the animation is complete.
Here are some examples of what I'd expect, and what happens.
Here's a code snippet ready to drop on a Playground to test (Xcode 15.3, iOS 17). Please note that it's just an example I tried to distill from the real code, which is more complex.
import UIKit
import PlaygroundSupport
var onTap = {
removeSection()
}
// Rainbow colored cells
final class Cell: UICollectionViewCell {
override func prepareForReuse() {
contentView.backgroundColor = .clear
}
func configure(with color: UIColor) {
contentView.backgroundColor = color
}
}
// The cell that when tapped, triggers the section removal (note that selection isn't being handled through didSelectItem.
final class RemovableCell: UICollectionViewCell {
override init(frame: CGRect) {
super.init(frame: frame)
contentView.backgroundColor = .systemCyan
contentView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(tap)))
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc
func tap() {
onTap()
}
}
// Registrations for the cells
let cellRegistration = UICollectionView.CellRegistration<Cell, Int> { cell, indexPath, _ in
let colors: [UIColor] = [.red, .orange, .yellow, .green, .blue]
cell.configure(with: colors[indexPath.item % colors.count])
}
let removableCellRegistration = UICollectionView.CellRegistration<RemovableCell, Int> { cell, indexPath, _ in }
let removableSection: NSCollectionLayoutSection = {
let itemSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1),
heightDimension: .estimated(120))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
let group = NSCollectionLayoutGroup.horizontal(layoutSize: itemSize, subitems: [item])
let section = NSCollectionLayoutSection(group: group)
return section
}()
// Sections definitions
let orthogonallyScrollingSection: NSCollectionLayoutSection = {
let itemSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1),
heightDimension: .fractionalHeight(1))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
let groupSize = NSCollectionLayoutSize(
widthDimension: .estimated(100),
heightDimension: .fractionalHeight(0.2))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
let section = NSCollectionLayoutSection(group: group)
section.orthogonalScrollingBehavior = .continuous
return section
}()
// Collection view construction, please don't mind the mess.
var removeSectionAvailable = true
let layout = UICollectionViewCompositionalLayout { section, _ in
if section == 0, removeSectionAvailable {
removableSection
} else {
orthogonallyScrollingSection
}
}
let frame = CGRect(x: 0, y: 0, width: 300, height: 500)
let view = UICollectionView(frame: frame, collectionViewLayout: layout)
let dataSource = UICollectionViewDiffableDataSource<Int, Int>(collectionView: view) { collectionView, indexPath, itemIdentifier in
if indexPath.section == 0, removeSectionAvailable {
return collectionView.dequeueConfiguredReusableCell(
using: removableCellRegistration,
for: indexPath,
item: itemIdentifier)
} else {
return collectionView.dequeueConfiguredReusableCell(
using: cellRegistration,
for: indexPath,
item: itemIdentifier
)
}
}
var snapshot = NSDiffableDataSourceSnapshot<Int, Int>()
snapshot.appendSections([0, 1])
snapshot.appendItems([0], toSection: 0)
snapshot.appendItems([1, 2, 3, 4, 5, 6], toSection: 1)
dataSource.apply(snapshot)
func removeSection() {
removeSectionAvailable = false
snapshot.deleteSections([0])
dataSource.apply(snapshot)
}
// Playground setup
PlaygroundPage.current.liveView = view
PlaygroundPage.current.needsIndefiniteExecution = true
All tips are appreciated!
Upvotes: 2
Views: 121