Working with UICollectionView and the layout to layout transition makes it very easy to animate between two flow layouts with minimal work.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) { if let dest = segue.destination as? DetailCollectionViewController { dest.useLayoutToLayoutNavigationTransitions = true } }
There are a couple of major gotchas that one needs to be aware of. The first is that the data source and delegate do not change. The initial view controller remains in charge.
Secondly, with custom subclasses of UICollectionViewCells that use autolayout to lay out their subviews, you need to override `apply(layoutAttributes:)` and call `setNeedsLayout()` in order to get those subviews to animate alongside the cells themselves.
Not doing this causes the cells to jump between their start and end state when the rest of the animation finishes.
Using Enums for Selected Items
Because the master collection view controller continues to be the data source, one nice thing I found is to use an enum for the selected section. This was inspired by this post by Soroush Khanlou.
The enum will either be none for no selected item, or a section index for a selected item:
class MasterCollectionViewController { enum SelectedItem { case item(Int) case none } // ... }
The model can then read from a computed property that will return either all of the items or only the selected items:
// Example model object struct Item { let name : String } // Example model collection var allItems : [[Item]] = [[Item(name: "First item"), Item(name: "Second item")], [Item(name: "Section 2, First item"), Item(name: "Section 2, Second item")]] var arrayOfSections : [[Item]] { switch self.selectedItem { case .none: return allItems case .item(let section): return [allItems[section]] } }
The collection view uses this computed property as its data source for things like `numberOfItems(inSection:)`.
This allows for some neat tricks where you can animate the addition or removal of all the other sections during the transition.
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { // Get the sections of all the items let allSections = IndexSet(integersIn: 0..<allItems.count) // Remove the one we're interested in let allButSelectedSection = allSections.subtracting(IndexSet(integer: indexPath.section)) // Keep a reference to it selectedItem = .item(indexPath.section) // Delete the sections collectionView.deleteSections(allButSelectedSection) // Start the layout to layout transition performSegue(withIdentifier: "detailSegue", sender: self) }
Because the computed property will now be updated to reflect only a single section, the collection view will animate the deletion of the sections nicely alongside the new layout transition, leaving the new layout with only a single section.
This is not ideal for complex detail views that allow for a lot of editing as the Master Collection View Controller is still in charge of all delegate and data source calls, but for a quick way of drilling down through a collection view hierarchy, it can save a lot of time and effort.