Resizing UICollectionview


#1

I’ve created a UICollectionView, so I want to display 2 columns if the device is in landscape or if the device is an iPad. Whats the most elegant solution for this problem?

Thanks.


#2

i’m not sure what you mean by “resizing”; but you can have specific number of columns by providing your collection view with a proper UICollectionViewLayout subclass. One such “grid” layout you can find here

if orientationIsLandscape { layout.numberOfColumns = 2 }


#3

What I meant with resizing was, for example. you have an UICollectionView in portrait and each cell is taking 100% width, then when you rotate the screen to landscape the same data should be presented in 3 columns by row for example. Thanks.


#4

hmm. i can’t tell where i’m misunderstanding you.

Suppose you have a collectionView with constraints to filling view width.
When orientation changes (if you handle it correctly) the following code will make collectionView present data in 1 column in portrait (“100% width”), and 3 columns in landscape

if landscape {
    gridLayout.numberOfColumns = 3
} else {
    gridLayout.numberOfColumns = 1
}

Is it not what you are trying to accomplish?


#5

I used size classes to achieve this on the NSScreencast app. You can override this method in your view controller:

override func willTransition(to newCollection: UITraitCollection, with coordinator: UIViewControllerTransitionCoordinator) {
    if newCollection.horizontalSizeClass == .regular { 
         myCollectionViewLayout.itemSize = // ...
    } else {
         myCollectionViewLayout.itemSize = normalSize
    }
}

This will give you more items in landscape on a Plus sized device, or iPads in any orientation.

For the NSScreencast app, I am switching out the layout entirely, so it required me to take note of the scroll position (in terms of which cell is in the upper left hand corner) and then restore that same scroll position after the update.

override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
    guard size != view.bounds.size else { print("Ignoring same size change..."); return }        
    adaptiveLayout.invalidateLayout()
    
    firstVisibleIndexPathBeforeSizeChange = indexPathForFirstVisibleItem()
    updateLayout(for: traitCollection, at: size)
    coordinator.animate(alongsideTransition: { context in
        if let firstIndexPath = self.firstVisibleIndexPathBeforeSizeChange {
            self.collectionView?.scrollToItem(at: firstIndexPath, at: .top, animated: false)
            self.firstVisibleIndexPathBeforeSizeChange = nil
        }
    }, completion: nil)
}

And the updateLayout method goes 1 step farther and gives more room for huge screens:

func updateLayout(for traitCollection: UITraitCollection, at size: CGSize) {
        var safeSize = size
    
    if #available(iOS 11.0, *) {
        safeSize.width -= view.safeAreaInsets.left + view.safeAreaInsets.right
    }
    
    switch traitCollection.horizontalSizeClass {
    case .unspecified, .compact:
        collectionView?.backgroundColor = NSScreencastTheme.collectionViewListBackgroundColor
        adaptiveLayout.configureTableLayoutWithSize(safeSize)
        
    case .regular:
        // iPad Pro in landscape is ridiculous
        let imageAspectRatio: Float = 1.77778
        let columns = size.width > 1024 ? 5 : 3
        adaptiveLayout.configureGridLayoutWithSize(safeSize, columns: columns, aspectRatio: imageAspectRatio)
        collectionView?.backgroundColor = NSScreencastTheme.collectionViewGridBackgroundColor
    }
    
    collectionViewLayout.invalidateLayout()
}

Hope this helps!


#6

Hello Ben,

thanks, cool. I just wanted to know from others how you solve this and compare it with mine, I did something similar, in my case iPhone only needs to support portrait mode, and support all orientations in iPad so I think was an easier solution, but I did it using size classes too and viewWillTransition.