This post is part of a series written by our developers that documents their journey in a cross-platform mobile app development project. They set out to improve an internal process, create educational content for the developer community, and explore new craft beers and a vintage arcade.

 

Welcome to the second of four parts in the Custom Layout for UICollectionView in iOS / Swift series. In the preview I covered the motivation behind using a collection view and set a goal of converting a simple list of data into a much nicer, custom design.

Simple List to Collection View

Figure 1

Ultimately we’ll end up with the screen similar to the contents of the dashed blue box on the right of Figure 1. Before we can get there we need a foundation to build on. This article will start things off by implementing a basic UICollectionView using the build-in flow layout. It won’t be much to look at but it will put things in great shape for starting the customization in the next article.

If you already familiar with the basics behind UICollectionView you might want to skip forward to the next article in the series.

BASIC UICOLLECTIONVIEW

CREATING THE VIEWS

We’ll start by adding a UICollectionViewController to the Storyboard. By default the view has a single reusable UICollectionViewCell. This reusable view will act as a template for items in the collection.

STEPS FOR CONFIGURING A REUSABLE COLLECTION VIEW CELL

  1. Add controls and other content to the cell
  2. Add tags to controls
  3. Apply auto layout constraints to handle layout inside the cell
  4. Style the contents of the cell
  5. Add a Reuse Identifier to the cell

Since each of our items just displays text we will add a single label to the cell. The data source will need a way to reference the label so we will tag it. The View section for the label in the Attribute Inspector has a numeric Tag attribute. Just change the value from zero to one.

We will use four auto layout constraints to force the label to fill the entire cell. The label needs to completely fill the cell so the constraints pin the leading, trailing, top and bottom space to the superview and not the superview’s margins. Hold down the alt key while control clicking to do this.

UICollectionViewCell's Label Contraints

 

Figure 2: Adding Auto Layout Constraints

Add a background color to the label and center the text after adding the constraints. Don’t worry too much about the current size of the cell. It won’t matter later when we start doing the custom layout. At this point the view should like something like the one below.

UICollectionView Label Cell

 Figure 3: A Simple Collection View Cell

Every reusable cell needs an identifier. This identifier will be used by the later by the data source to find the correct type of cell. Click on Collection View Cell in the scene outline and set the Collection View Cell Identifier in the Attribute Inspector. We’ll call this cell timeEntry.

Our layout also requires a section header. Headers are enabled by clicking the Section Header checkbox in Attribute Inspector for the Collection View. The header requires two labels. One label for the date and another for the number of hours recorded in that day. Add a unique tag to each label, the date label will be one and the hours label will be two.

We want the labels to be evenly spaced with each one taking up half the width of the header. This requires a few additional layout constraints. First pin the date label (the one on the left) to the top, bottom and left of the superview. Next pin the hours label (the one on the right) to the top, bottom and right of the superview.

The trickiest part are the two constraints that force the labels to each take up half of the available width. We will add one constraint that makes each label an equal width. Do this by control dragging from the date label to the hours label and selecting Equal Widths. The second constraint will force the trailing edge of the date label to equal the leading edge of the hours label. Control drag from the hours label to the date label in the scene outline of the Storyboard and select Right.

 

Figure 4: Layout Constraints for Equal Width Labels

You will need to modify the details of the Right constraint using the Attribute Inspector. Click on the constraint in the scene outline to display the details. The trailing edge of the Date label should be equal to the leading edge of the hours label.

Horizontal Space Constraint DetailsEqual Width Constraint Details

Figure 5: Layout Constraint Attributes

Again set background colors for the header labels and center the text. Now the collection view looks like this.

Completed Collection View Cells

 

Figure 6: Collection View Header Cell

As with the time entry cell the header cell needs a reuse identifier. We will call this cell the dayHeaderCell.

CREATING THE DATA SOURCE

The data source is an object that conforms to the UICollectionViewDataSource protocol. Since our view controller inherits from UICollectionViewController we will simply override a few methods. These methods tell UICollectionView how many sections we have, how many items are in each section, and provide views to be display in each section.

ORGANIZE THE DATA

The data source will be much easier to implement if your data is organized properly. Items in collection are identified using a section number and item number. The simplest way to organize the data is a two dimensional array. The first index represents the section and the second index represents the item. In our case we’ll introduce a small struct (day) to hold header data along with the items (entry).

struct day {
    let date: String
    let entries: [entry]
}

struct entry {
    let client: String
    let hours: Int
}

let sampleDataByDay: [day] = [
    day(date: "Mon 8/3", entries: [entry(client: "Microsoft", hours: 8)]),
    day(date: "Tue 8/4", entries: [entry(client: "Google", hours: 2), entry(client: "Apple", hours: 6)]),
    day(date: "Wed 8/5", entries: [entry(client: "Apple", hours: 8)]),
    day(date: "Thu 8/6", entries: [entry(client: "Microsoft", hours: 4), entry(client: "Microsoft", hours: 4)]),
    day(date: "Fri 8/7", entries: [entry(client: "Google", hours: 8)]),
    day(date: "Sat 8/8", entries: [entry(client: "Intertech", hours: 6)]),
    day(date: "Sun 8/9", entries: [])
]

Proper organization of the data makes implementing the first two UICollectionViewDataSource methods very easy. The number of sections is the count of the outer array. The number of items in each section is just the count of entries in a day.

class TimeEntryCollectionViewController: UICollectionViewController {
    override func viewWillAppear(animated: Bool) {
        collectionView!.reloadData()
    }
    override func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
        return sampleDataByDay.count
    }
    override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return sampleDataByDay[section].entries.count
    }
    // Code removed
}

We need to implement two more methods to complete the data source. These methods populate the reusable cells that we created earlier with the actual data. The first of these two methods provides the timeEntry cells.

The first line in the method dequeues a UICollectionViewCell from the UICollectionView. We identify the type of cell by providing the reuse identifier along with the index path. The remaining code finds the correct entry in our sample data and constructs the label text to be displayed. Again the organization of the data makes it easy to find the correct entry.

class TimeEntryCollectionViewController: UICollectionViewController {
    // Code removed
    override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCellWithReuseIdentifier("timeEntryCell", forIndexPath: indexPath) as! UICollectionViewCell
        
        let timeEntry = sampleDataByDay[indexPath.section].entries[indexPath.item]
        let label = cell.viewWithTag(1) as! UILabel
        label.text = "\(timeEntry.client) (\(timeEntry.hours))"
        
        return cell
    }
    // Code removed
}

The final method for the data source handles the header cell. The code is very similar to the previous method however this time we dequeue a UICollectionReusableView. This type of view is used for supplementary views like a header or footer.

class TimeEntryCollectionViewController: UICollectionViewController {
    // Code removed
    override func collectionView(collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, atIndexPath indexPath: NSIndexPath) -> UICollectionReusableView {
        let cell = collectionView.dequeueReusableSupplementaryViewOfKind(kind, withReuseIdentifier: "dayHeaderCell", forIndexPath: indexPath) as! UICollectionReusableView;
        
        let day = sampleDataByDay[indexPath.section];
        let totalHours = day.entries.reduce(0) {(total, entry) in total + entry.hours}
        
        let dateLabel = cell.viewWithTag(1) as! UILabel
        let hoursLabel = cell.viewWithTag(2) as! UILabel
        
        dateLabel.text = day.date
        hoursLabel.text = String(totalHours)
        
        return cell
    }
}

LAYOUT OBJECT

So far we have added the views and data source for a basic UICollectionView. We have determined the position of the UICollectionView by placing it on the Storyboard inside a UICollectionViewController. It will fill the entire screen at runtime. We also determined how content inside our cells will be positioned by adding auto layout constraints. But what about the layout of the cells themselves? That’s the job of the layout object.

UICollectionView comes paired with just one layout class, the UICollectionViewFlowLayout. The flow layout places items in a horizontal or vertical row. This layout may look familiar. It is used for displaying previews in the Photo app.

RUN THE CODE ALREADY!

It’s time to run the sample and see our new improved time entry view. Wait a minute. This doesn’t look anything like the awesome visualization that I promised up front. Fear not; it might not look like it but we are surprisingly close to our final goal. In the next article in the series we will replace UICollectionViewFlowLayout with a custom implementation. Rearranging the cells that we already have will make this view almost unrecognizable from its current form.

Basic Collection View

 

Figure 7: Time Entry Collection Using Flow Layout

 

All Parts of Series:
&nbsp