Search code examples
iosswiftuicollectionviewaccessibilitydynamic-type-feature

UICollectionView autosize cell with dynamic number of columns using Accessibility


I'm trying to mimic the behavior of the current Shortcuts app UICollectionView where the default display of items is in a 2 column layout, but depending on the font size, it changes to a single column with the cell taking up the full width of the collection view.

I'm adding Dynamic Type to my cell label and if users go to phone Accessibility and increases the font size, my labels increase as well, but right now the label font increases but the cell only grows in height and my collection view ends up being two columns with very tall cells. example of what I'm trying to reproduce

I'm currently using basic Flow Layout without any customizations. I don't have a working or good code to share, but I'm looking for suggestions on best approach to accomplish this.

I'm targeting iOS 14, and using swift 5.

EDIT Thanks to Witek's answer I was able to accomplish this very very easily using UICollectionViewCompositionalLayout and I didn't even had to subscribe to changes, just querying for traitCollection inside my layout was enough. this is my final result:

private func createCompositionalLayout() -> UICollectionViewLayout {
    let layout = UICollectionViewCompositionalLayout { (sectionIndex: Int,
                                                        layoutEnvironment: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection? in
        
        let columns = self.traitCollection.preferredContentSizeCategory.isAccessibilityCategory ? 1 : 2
        
        // Define Item Size
        let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(1.0))
        
        // Create Item
        let item = NSCollectionLayoutItem(layoutSize: itemSize)
        
        // Define Group Size
        let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .estimated(150.0))
        
        // Create Group
        let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitem: item, count: columns)
        group.interItemSpacing = .fixed(10.0)
        
        // Create Section
        let section = NSCollectionLayoutSection(group: group)
        
        // Configure Section
        section.contentInsets = NSDirectionalEdgeInsets(top: 10.0, leading: 10.0, bottom: 10.0, trailing: 10.0)
        
        section.interGroupSpacing = 10
        return section
    }
    
    return layout
}

Solution

  • You can observe the UIContentSizeCategory.didChangeNotification and change the number of columns (cell size) based on the newValueUserInfoKey value passed in the notification.

    @objc private func sizeCategoryDidChange() {
        collectionView.reloadData()
    }
    
    NotificationCenter.default.addObserver(
        self, selector: #selector(sizeCategoryDidChange),
        name: UIContentSizeCategory.didChangeNotification, object: nil
    )
    

    Preferred content size category can also can easily be retrived from UIApplication.shared

    let contentSizeCategory = UIApplication.shared.preferredContentSizeCategory
    

    It looks like the Shortcuts app does the layout based on the isAccessibilityCategory property to determine whether the current content category is one of the accessibility ones

    let cellWidth: CGFloat
    let padding: CGFloat = 16
    if contentSizeCategory.isAccessibilityCategory {
        // single column, full width with padding
        cellWidth = collectionView.bounds.width - (padding * 2)
    } else {
        // double column, half width with padding
        cellWidth = (collectionView.bounds.width - (padding * 3)) / 2 
    }
    

    Documentation: