In my previous post I demonstrated how to use AutoLayout and make UIImageView resize to full screen when in landscape mode. While happy with the outcome - the solution was not exactly what I wanted in the 1.2 version of Gun Vault. I wanted to allow to user to view all images from the item details screen using a scrollable carousel-style UICollectionView. I still wanted to have the carousel resize to full screen when in landscape mode as well prepare for future iPad oriented optimizations. It turned out that making a UICollectionView behave the same way as a UIImageView is much more difficult so I hope this article saves you time if you are trying to accomplish something similar so let’s get going.
Our view controller will contains a UICollectionView and few sample images
Let’s go over the code.
In viewDidLoad() we simply build our UI - we add a UICollectionView as a subview to the MainController’s view and turn on paging. Line 53 is important because othwerise we’ll get a bunch of errors around insets.
In traitCollectionDidChange() we do most of the heavy lifting around AutoLayout. While the instructions look familiar, you might be used to seeing them in viewDidLoad() instead. The traitCollectionDidChange() method is actually equally if not better suited for setting up all AutoLayout stuff with the added benefit of keeping your viewDidLoad() slimmer. Similar to viewDidLoad() it also gets called on initial load but unlike viewDidLoad() it gets called again when there are changes to the UI - in example when the device is rotated or when on iPad a view is resized. As usual - there are some gotchas when it doesn’t happen but it is perfect for our use case. We’ll use it to set up some common constraints that will remain the same no matter what the orientation is as well as use it as a trigger to activate/deactivate portraint or landscape constraints only - show/hide the navbar, resize the collection view to full screen and back and so on.
The only other thing we need is a UICollectionViewCell with a UIImageView inside.
If we run the example and rotate the device everything will look as expected as long as we stay on the first image. As soon as we go to the next and rotate again we’ll uncover our first bug.
The reason for our bug is very simple - we turned on paging but we forgot to instruct our collection view how to handle changes in page size so let’s go ahead and do that. We’ll add the following method to our MainController
The code in the viewWillTransitionToSize() method will figure out the current page and will instruct the transition coordinator to adjust the collection view’s offset to the appropriate X coordinate as part of the animation during rotation. This method is relatively new so I added few print statements to show you what gets called when. If you run the app now you will see that our bug is fixed.
While it is mission accomplished already as far as the main use case goes, those of us that are a bit more detail oriented have probably already noticed the weird artifacts happening during rotation. If you are not one of them - run the app again and pay closer attention - do you see the flickering now? I bet you do. Let’s go ahead and address it.
Rather than tinckering with the UICollectionView’s animation we’ll use a very simple trick using a hidden UIImageView with the same constraints as the UICollectionView.
- Before rotation: take a snapshot of the currently displayed image and load it in the image view; hide the collection view and show the image view.
- During rotation: rotate both the image view and the collection view
- After rotation: hide the image view and show the collection view
Let’s add our image view to the controller as well as update the AutoLayout instructions.
And finally, let’s update our viewWillTransitionToSize() method to do the swapping trick
And that’s it. Now we have a much nicer UICollectionView with AutoSizing support for iPhone and iPad orientation changes.