• 20 hours
  • Medium

Free online content available in this course.

course.header.alt.is_video

course.header.alt.is_certifying

Got it!

Last updated on 3/6/20

Discover grid presentation using collection views

Log in or subscribe for free to enjoy all this course has to offer!

In this chapter, we are going to discover another convenient, flexible, and efficient tool to present dynamic content: collection view.

Collection view

Collection view is represented by  UICollectionView  class and incorporates a set of sub-elements that allows to display items in the form of a grid.

The direct sub-elements of a collection view are:

  • Header

  • Footer

  • Items. In the context of the collection view, the term item refers to a cell. 

Cells can be grouped into sections... sound familiar? ;)

Those definitions may remind you of table view ( UITableView ) - and rightly so, as they share many concepts. 

Let's add a collection view object to the list view controller on the storyboard:

Add collection view
Add collection view

Make sure that it's positioned and sized properly - we want it to take up all of the available space on the screen:

Size and position collection view
Size and position collection view

In order to make our view controller code work with the view object, we need it to implement delegate protocols -  UICollectionViewDelegate  and  UICollectionViewDataSource  .

We could do that in the subclass of our  UIViewController  class:

class MediaListViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {
...
}

This time around we'll use a different, more elegant approach - to achieve the same result - using extensions.

Extensions

Extensions are literally extensions on a class. They allow you to extend a class without subclassing or modifying the original class.

Why is it "cooler" than subclassing or modifying the original class?

Subclassing forces us to use the subclass, which means that we have to modify our code if we want to use the implemented functionality.

And modifying the original class may become too clumsy. Besides, we can't modify the Swift API classes, but we certainly can extend them! 😏

When working with collection views within our view controllers, we are adding functionality to our custom subclass - MediaListViewController - which conforms to required collection view protocols. This will allow us to keep the code clean and to easily separate.

Could we just use #MARK to separate the code?

Using #MARK is one solution; however, in terms of code management, extensions provide the ability to distribute code between different files!

To implement an extension, use the keyword  extension  instead of  class :

extension MediaListViewController: UICollectionViewDataSource {
}
extension MediaListViewController: UICollectionViewDelegate {
}

Next, we need to connect our collection view to the code to create an outlet...

@IBOutlet weak var collectionView: UICollectionView!

...and implement a helper  config  method that will assign the data source and delegate:

override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
config()
}
func config() {
collectionView.delegate = self
collectionView.dataSource = self
}

And now, of course, we get some complaints that we don't conform to the declared protocols; the required methods are for the data source and are very similar to those needed for the table view. To serve the data, let's declare a property that will temporarily serve an empty array of objects we're going to use to populate the grid:

var dataSource: [MediaBrief] {
return [MediaBrief]()
}

And then complete the conformance to the dataSource protocol:

extension MediaListViewController: UICollectionViewDataSource {
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return dataSource.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
return UICollectionViewCell()
}
}

 Where else can we use extensions?

We can use extensions for many purposes, including adding or altering the functionality of existing Swift classes. For example, we can programmatically implement custom colors beyond those that are provided by the UIKit framework and use them as if they were always there. 🎨

Here's a simple example:

extension UIColor {
static let sunshine = UIColor.yellow
static let forest = UIColor.green
struct UIPalette {
static let backgroundColor = UIColor.sunshine
static let titleColor = UIColor.forest
static let subtitleColor = UIColor.blue
}
}

And then, we can use it in our code as if the features of the extension were part of the original class:

view.backgroundColor = .sunshine
view.backgroundColor = UIColor.UIPalette.backgroundColor

Notice how we also grouped the palette colors into a structure? It makes the code more readable and easier to maintain.

Could we just assign colors directly?

We could assign the palette colors to the UI elements directly; however, the code is more flexible when specific colors are abstracted. For example, should we decide to use green as an accent color, we'd only need to reassign a new color to the UI color abstraction which would then appear in all parts of the code! ✨

Extensions are a very powerful and useful component of Swift language!

Understanding the collection view flow layout

Collection view offers flexibility in the way we lay out elements (its items, or cells). Dealing with the layout requires an associated object called "Collection View Flow Layout" represented by a class  UICollectionViewFlowLayout .

Let's take a closer look at the storyboard navigation. When we dragged a collection view object to our list view, it was added with two sub-elements:

  • Collection View Cell - an expected cell prototype that we'll utilize shortly

  • Collection View Flow Layout

Collection View Flow Layout
The Collection View Flow Layout

The collection view flow layout is responsible for the direction of items scrolling on the collection view, item sizing, etc. It's represented by a class  UICollectionViewFlowLayout , and we need to create an outlet for it in order to be able to customize it programmatically.

@IBOutlet weak var collectionViewFlowLayout: UICollectionViewFlowLayout!

For now, we are going to only set the scroll direction in the config method:

collectionViewFlowLayout.scrollDirection = .vertical

We can also specify various general attributes. Some handy ones are:

  • estimatedItemSize  or  itemSize  - to provide an estimated size for the items or an exact size, respectively.

  • minimumInteritemSpacing  or  minimumLineSpacing  - to provide spacing between items and lines, respectively.

We can also provide individual values based on section and even item within each section. For that, our view controller must conform to  UICollectionViewDelegateFlowLayout  protocol, which we can also declare as an extension:

extension MediaListViewController: UICollectionViewDelegateFlowLayout {
}

Now we can place our sizing and spacing delegate methods within it:

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let w = collectionView.frame.size.width
return CGSize(width: (w - 20)/2, height: 290)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 20
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 30
}

Let's do one more thing to this view - set a navigation title programmatically in the config method:

navigationItem.title = "ListenUp!"

It's time for a quick test - click Run!

ListenUp!
ListenUp!

Not much going on at the moment...

No wonder! We haven't provided any items as data source. If we did, the app would crash as we haven't provided any suitable cells in the collection view cellForItem method. You can try it out yourself - change the dataSource property to return sample data.

Storyboard vs programmatic implementation

You can also configure many of the properties of both collection view and its flow layout using Attribute inspector in storyboard.

On the other hand, you can also create all the UI elements programmatically instead of adding and configuring them in the storyboard.

Which approach to choose?

It depends. πŸ˜… Using storyboards is always a beneficial way to start and play around. As projects and teams grow, it might be challenging to maintain more sophisticated storyboards. They are often abandoned altogether in favor of programmatic implementation of interface.

It's also possible to use a mix-and-match approach and implement visually select components and reuse them programmatically in the code.

Moving on to further customization - we need the cells for data! 😁

Let's recap!

  • Collection views provide a grid layout for content elements and are represented by the class  UICollectionView .

  • Collection view delegate (typically a view controller) must conform to two protocols:  UICollectionViewDelegate  and  UICollectionViewDataSource .

  • To complete the minimal conformance to the delegate protocols, the delegate must implement all required methods of  UICollectionViewDataSource  protocol.

  • Extensions can be used to extend or alter the functionality of classes, structures, protocols, or enumerations without subclassing or modifying the original implementations.

  • Extension functionality becomes available (as if it were part of the original object) anywhere in the code that has access to the extension.

  • Collection items layout is managed by a component called collection view flow layout, which is represented by the class  UICollectionViewFlowLayout  and controls various characteristics, i.e. the direction of scrolling or item sizing.

Example of certificate of achievement
Example of certificate of achievement