Creating a Scrolling Filmstrip Within a UITableView
While working on our current project, our UI designer came up with a mockup that displayed photo albums as a small horizontal scrolling filmstrip within a single table cell of a larger table view.
Looking around the internet, I found several examples of a horizontal scrolling
UITableView created inside another
UITableView, much like the Pulse app. Creating ‘Pulse’ style scrolling
You could do something along the same lines with a
UIScrollView, but I thought using a
UICollectionView might be a cleaner implementation.
UICollectionViews are new as of iOS 6.0, and they give developers an elegant way to build grid views well beyond what
UITableView is capable of.
Here’s the basic rundown of how this works:
- Create a normal
UITableViewwith a custom
- Create a custom
UIViewthat will be added to the cell’s contentView
- The custom
UIViewwill contain a
- The custom
UIViewwill be the datasource and delegate for the
UICollectionViewand manage the flow layout of the
- Use a custom
UICollectionViewCellto handle the collection view data
NSNotificationto notify the master controller’s
UITableViewwhen a collection view cell has been selected and load the detail view.
Here’s an image of what the view should look like:
If you want to learn more about
UICollectionViews, Ray Wenderlich has a great tutorial Beginning UICollectionView In iOS 6. You can also watch the WWDC2012 videos Introducing Collection Views and Advanced Collection Views and Building Custom Layouts on iTunes.
Before I go into details, you can grab the sample project on github: HorizontalCollectionViews
Here’s how to build this out:
1) Create a new iOS project in Xcode.
Select a Master/Detail application. Use automatic reference counting, but turn off storyboards and unit tests. This creates a project with both a master view controller and detail view controller. The master is a
UITableViewController, which is exactly what we need to start with.
2) Let’s make some initial changes to the MasterViewController.
For simplicity, change the
canEditRowAtIndexPath method to return NO. Comment out the
commitEditingStyle... method. Inside
viewDidLoad, remove the code that adds
rightBarButtonItem. You can also delete the
We need to set up sample data. In our example, each row will represent a collection of data. For each row, I’m using a dictionary that contains a title for the section heading. We’ll use an array of strings as the data in the cell’s collection view.
Inside MasterViewController, change the ivar _objects to an
NSArray, then add the following code inside
3) Create a custom
UIView and call it ContainerCellView. Create a nib file for it as well.
This will be the custom content view that our
UITableViewCell will use. It will also contain all of the logic for the
View the nib file in Interface Builder. Go to the Attributes inspector, and set the size to be freeform and the status bar to none. Set the background color to white. Go to the size inspector and set the size to a width of 320 and a height of 180. Also, go to the Identity inspector and set the Class to your class name (ContainerCellView).
Next, drag a
UICollectionView from the Data Objects panel onto the view. Let the collection view fill the view.
The custom view needs one public method called
setCollectionData that accepts an
The ContainerCellView.m has a lot going on. We’ll walk through each part.
UICollectionViewDelegate protocols. Create an
IBOutlet property for the collection view. Create a property for the collection data.
Go back into IB and connect the
IBOutlet from the ContainerCellView object to the
UICollectionView. Set the ContainerCellView as the delegate for the
Go back to ContainerCellView.m and add the following:
The method awakeFromNib does a couple of things. It sets the background of the collectionView. It also registers a custom collectionViewCell with the collection view. This is something new with iOS6, and it allows you to register any custom cell views with a table or collection view. It simplifies the code when you get into the cellForRowAtIndexPath method. If you’re using a nib file, you’ll have to make sure to set the Reuse Identifier in IB.
It also creates a UICollectionViewFlowLayout and sets both the item size and scroll direction. UICollectionViewFlowLayout is one aspect where UICollectionView varies from UITableView. You have a couple of approaches to using a flow layout, but you must have one. You can define one in code and set the properties like I have above. Or, you can use the UICollectionViewDelegateFlowLayout protocol and configure layout properties at runtime.
Next, we need to implement the setCollectionData method. This will set the collection view data, and then reload the collection view.
Next, implement the UICollectionViewDataSource methods. Set the number of sections to 1. Set the numberOfItemsInSection to be the collectionData count. Then implement cellForItemAtIndexPath.
In the above code, I’m getting the custom UICollectionViewCell that I registered in awakeFromNib. Since I registered it, there’s no longer a need to check for nil here.
4) Create a custom UICollectionViewCell and call it ArticleCollectionViewCell. Create a custom nib file for it as well.
Set the size of the cell to be 130×170. Add a UILabel to the cell (I called mine articleTitle). Be sure to create a public IBOutlet property for the label and wire it up in IB. Also in IB, make sure to set the Class name to your class name (in my case, ArticleCollectionViewCell) and the reuse identifier to your class name.
5) Create a custom UITableViewCell and call it ContainerTableCell.
This is the custom cell our MasterViewController table view will use. We need to do a couple of things here.
Add a public method called setCollectionData that takes an NSArray to the interface. Add a private strong property for a UIView. I’ve called mine collectionView. This will be a reference to the custom UIView that will contain the collection. Implement setCollectionData. Here’s the code:
The initWithStyle just loads our custom view from the nib file. Then we add it to the table cell’s contentView. setCollectionData hands the array of data off to the collectionView.
6) Wire things up to the MasterViewController.
So, first we need to register our custom container cell with the MasterControllerView’s tableView. So add this line into viewDidLoad.
Because we created the custom containerCell without a nib file, we register the class.
Next, update all of the UITableViewDataSource and delegate methods. For fun, I’m pulling the description property and using that as the section header of the table cells. Each row will be a separate custom cell with a UICollectionView inside it.
The snippets include everything for the cell and the section headers.
7) Test it out!
If you have all of this set up and run, you should see something like the image at the beginning of the article. If not, here are a few things to check:
Make sure the containerTableCell initialized the containerView and add it to the cell’s contentView. Check that you’re getting the list of articles for the table row. Check that the list of articles is getting passed down to the containerCell via setCollectionData:. Make sure the UICollectionView is correctly connected to the containerView and that the containerView is the datesource and delegate 8) Selecting a CollectionViewCell
Now, you want to be able to select a collection view cell and pass the cell’s data back up to the MasterViewController so it can send that data to the DetailViewController.
I chose to do this with NSNotifications, but you could also set the MasterViewController as the UICollectionView’s delegate. To set things up with notifications, here’s what I did:
Inside MasterViewController, I set up the observer for the notification in viewDidLoad.
Then I commented out the code for didSelectRowAtIndexPath. If you leave this code in, you’ll always select the table view cell, not the individual collection view cell.
I also set up the selector method that looks for the dictionary of cellData and passes it to the detailViewController. I then have the navigation controller load the detailViewController.
Then, I implemented the didSelectItemAtIndexPath method inside the containerView. This posts a NSNotification and sends the current cell’s data.
Finally, I updated the configureView method of the detailViewController to pull the title from the data.
That’s it! If you had everything working before, running the app now should allow you to select a specific article cell, and the detail view should show the title you selected.
Using UICollectionViews inside UITableViews may not be something Apple intended us to do, but it does create some interesting ideas for laying out complex sets of data.
Feel free to grab the example project and take a look at it: HorizontalCollectionViews
Jim Clark has over 15 years of design and development experience. After graduating from Texas Tech with a bachelor's degree in Design Communication, he started out doing graphic design, then found a unique opportunity to do QA testing/UI design for Macromedia's FreeHand. From there, he moved onto the web, starting out designing and building websites before graduating to building web applications. Jim has spent the last 12 years designing and building web-based applications for the enterprise. Last year, he started his own company focused on building mobile applications. In his free time, Jim continues to tinker with new technologies, writing, and occasional forays into World of Warcraft.
comments powered by Disqus