I'm wanting on creating expanding cells using Compositional Layout. An issue i'm facing is that every time when my cell expands, i'm getting this unwanted offset change occurring right after my expansion is complete (moving cells to the right). For the life of me, im not understanding why its scrolling. Looking at the expansion state, its not like there were wasnt enough room on the left side of the screen. So im unsure of what's causing this and if we can manually turn it off. Any suggestions would be appreciated!
class ViewController: UIViewController, UICollectionViewDelegate {
lazy var collectionView: UICollectionView = {
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: createLayout())
collectionView.translatesAutoresizingMaskIntoConstraints = false
collectionView.delegate = self
collectionView.contentInsetAdjustmentBehavior = .never
collectionView.alwaysBounceVertical = true
collectionView.alwaysBounceHorizontal = true
return collectionView
}()
var dataSource: UICollectionViewDiffableDataSource<Section, Item>!
var items = [Item]()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(collectionView)
setupCollectionViewDataSource()
...
}
func setupCollectionViewDataSource() {
dataSource = UICollectionViewDiffableDataSource(collectionView: collectionView, cellProvider: { collectionView, indexPath, itemIdentifier in
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ProminentCell.cellId, for: indexPath) as! ProminentCell
cell.backgroundColor = self.colors.randomElement()
cell.numberLabel.text = String(indexPath.item)
cell.delegate = self
return cell
})
updateDataSource()
}
func createLayout() -> UICollectionViewLayout {
let layout = UICollectionViewCompositionalLayout { sectionNumber, env in
let itemSize = NSCollectionLayoutSize(widthDimension: .estimated(325), heightDimension: .absolute(708))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
let group = NSCollectionLayoutGroup.horizontal(layoutSize: itemSize, subitems: [item])
let section = NSCollectionLayoutSection(group: group)
section.orthogonalScrollingBehavior = .continuous
section.interGroupSpacing = 10
return section
}
return layout
}
private func updateDataSource() {
var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
snapshot.appendSections([.carousel])
snapshot.appendItems(items)
dataSource.apply(snapshot, animatingDifferences: true, completion: nil)
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if let cell = collectionView.cellForItem(at: indexPath) as? ProminentCell {
cell.currentState = .expanded
}
}
func collectionView(_ collectionView: UICollectionView, didUpdateFocusIn context: UICollectionViewFocusUpdateContext, with coordinator: UIFocusAnimationCoordinator) {
guard let nextIndexPath = context.nextFocusedIndexPath, let prevIndexPath = context.previouslyFocusedIndexPath else { return }
collectionView.isScrollEnabled = false
collectionView.scrollToItem(at: nextIndexPath, at: .left, animated: true)
}
}
extension ViewController: ProminentCellDelegate {
func expandCell(cell: ProminentCell) {
let snapshot = dataSource.snapshot()
dataSource.apply(snapshot, animatingDifferences: true, completion: nil)
}
}
Cell Class
protocol ProminentCellDelegate: AnyObject {
func expandCell(cell: ProminentCell)
func collapseCell(cell: ProminentCell)
}
enum ProminentCellState {
case collapsed, expanded
}
class ProminentCell: UICollectionViewCell {
static let cellId = "cellId"
weak var delegate: ProminentCellDelegate?
var currentState: ProminentCellState {
didSet {
if currentState == .collapsed {
collapseCell {}
} else {
expandCell(completion: {})
}
}
}
let imageStringName = ""
lazy var backgroundImageView: UIImageView = {
let imageView = UIImageView(image: UIImage(named: imageStringName))
imageView.backgroundColor = .systemOrange
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.adjustsImageWhenAncestorFocused = true
return imageView
}()
let numberLabel: UILabel = {
let label = UILabel()
...
return label
}()
private let expandedWidth = 1035.0
private let collapsedWidth = 325.0
private let animationDuration = 0.33
var closedConstraint: NSLayoutConstraint? = nil
var openConstraint: NSLayoutConstraint? = nil
override init(frame: CGRect) {
currentState = .collapsed
super.init(frame: frame)
contentView.addSubview(backgroundImageView)
contentView.addSubview(numberLabel)
contentView.translatesAutoresizingMaskIntoConstraints = false
let padding = 10.0
NSLayoutConstraint.activate([
contentView.topAnchor.constraint(equalTo: topAnchor),
contentView.leadingAnchor.constraint(equalTo: leadingAnchor),
contentView.trailingAnchor.constraint(equalTo: trailingAnchor),
contentView.bottomAnchor.constraint(equalTo: bottomAnchor),
backgroundImageView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: padding),
backgroundImageView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: padding),
backgroundImageView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -padding),
backgroundImageView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -padding),
numberLabel.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
numberLabel.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
])
closedConstraint = contentView.widthAnchor.constraint(equalToConstant: collapsedWidth)
closedConstraint?.isActive = true
openConstraint = contentView.widthAnchor.constraint(equalToConstant: expandedWidth)
}
private func expandCell(completion: () -> Void) {
closedConstraint?.isActive = false
openConstraint?.isActive = true
let curFrame = frame.origin
let expandedSize = CGRect(x: curFrame.x, y: curFrame.y, width: expandedWidth, height: frame.size.height)
UIViewPropertyAnimator.runningPropertyAnimator(withDuration: animationDuration, delay: 0, options: [.curveLinear]) {
self.frame = expandedSize
self.layoutIfNeeded()
self.contentView.layoutIfNeeded()
}
delegate?.expandCell(cell: self)
}
private func collapseCell(completion: () -> Void) {
openConstraint?.isActive = false
closedConstraint?.isActive = true
let curFrame = frame.origin
let collapsedSize = CGRect(x: curFrame.x, y: curFrame.y, width: collapsedWidth, height: frame.size.height)
UIViewPropertyAnimator.runningPropertyAnimator(withDuration: animationDuration, delay: 0, options: [.beginFromCurrentState]) {
self.frame = collapsedSize
self.contentView.layoutIfNeeded()
self.layoutIfNeeded()
}
delegate?.collapseCell(cell: self)
}
...
}
The solution was to turn the orthogonal scrollView isScrollEnabled to false. This removes the native scroll animation that occurs when scrollView's content size changes.