Implementing UICollectionView with horizontal scrolling and dynamic cell sizes
One of my magic hours app feature will be the ability to search for a location and save it for the future use. I need a control that will display my stored locations and I decided that I’ll place it at the bottom of the app’s main screen with the ability to scroll them horizontally. This can be achieved with UICollectionView control. There’s a great introduction on the topic by Matteo Manferdini: The correct way to display lists in iOS and what many developers do wrong. I tried to follow his advice and implement my solution the right way :)
So I started by drag and dropping CollectionView control to my view and placing it at the bottom of my layout.
There’s the UICollectionView control itself and a cell placed directly under it. A cell is part of user interface that displays information about a single item from a collection. My cell contains only label for a location name.
The next step is providing a custom class, I called it LocationCell, deriving from UICollectionViewCell that will be dealing with my cell representing a location. This is my final implementation:
Custom class has to be linked to cell in identity tab:
I had to also provide a string identifier for the cell, this can be any string, I chose my class name.
The next step was ctrl dragging the label to my new custom class to create an outlet locationNameLabel. I use this outlet for setting label’s contents to a location name and to change text color to yellow after the cell becomes selected. Collection and table views in iOS are designed to work with data source and delegate objects. Data source is an object that provides collection for the view and delegate is for dealing with cell sizes, selection changed actions and so on. I made one class conform to both protocols: UICollectionViewDataSource and UICollectionViewDelegateFlowLayout. In more complex scenarios this should be separated into two classes.
Object dealing as data source has to implement these two methods:
I initailize my data source object with list of my locations. The first method returns the length of array holding locations. In the second I’m dealing with my custom cell class. Setting locationName on cell object sets proper text in cell’s label through the outlet I’ve created.
Linking collection view control with data source object is done in main view controller in viewDidLoad method:
where locationsList is outlet to UICollectionView control placed in my view.
After launching the app I saw this:
Labels in my collection are of fixed size and don’t expand to its contents. This doesn’t look good. I started googling for a solution, I thought this would be like one switch somewhere. Nope. This is where the delegate object enters the scene.
With this method I can specify how long my cell should be. This also means that I have to measure the length of string with specified font..
Measuring is done in extension method:
I don’t really like the idea of having to know in code the exact font that will be used for displaying the location name. Maybe there is an easier solution? It took me long time to come up with a working solution but wouldn’t mind to change it :)
The last thing to implement is changing the location after tapping (selecting) the item in the list. First of all, make sure you set allowsSelection property to true:
The didselectItem tells the delegate that the item at the specified index path was selected. The collection view calls this method when the user successfully selects an item in the collection view. I initialize my data source object with a delegate function that sets the new location with view. I could have added a reference to my main view controller, but I wanted to keep these two separate.
This is the final implementation of LocationDataSource class:
For now, I’m playing around with some sample locations. Did you know that Qaanaaq is one of the northernmost towns in the world? :)