Let’s continue our journey through the
UICollectionView
class. Creating cells for UICollectionView
is really easy, moreover you can use the same approach for UITableView
. Let’s take a look at how to do it using Swift.
You will learn how to create simple cells with text and images in them, how to handle taps, and at the end of the article I’ll talk about how to use a xib file to create a cell.
In this tutorial I presume that you’ve already finished my previous tutorial — How to create UICollectionView using Swift without storyboards, because here I’ll use our finished Xcode project. If you didn’t, then don’t worry, the only thing you need here is working UICollectionView
.
So, in previous tutorial we used just a UICollectionViewCell
. It’s the cell that represents an empty view. The view that has no subviews at all.
If you want to add something to it like labels or images, you have to make a subclass of UICollectionViewCell
.
Preparations
-
1. Select File — New — File… or just press ⌘N.
2. Choose Source under iOS on the left side of the window. Select Cocoa Touch Class and click Next.
3. Name your cell class whatever you like. I’ll name it
RDCell
. In Subclass field select (you can also write here) UICollectionViewCell
. We don’t need a XIB file, you probably know why 😉 So make sure that Also create XIB file option is unchecked. Choose Swift as a language for this class.4. Click Next, and then Create to create a new class.
This is the class of our cell. Before we add something to it, let’s set this class as a cell for our UICollectionView
. Go to ViewController.swift
file, where we have our UICollectionView
set up. Replace this piece of code
collectionView!.registerClass(UICollectionViewCell.self, forCellWithReuseIdentifier: "Cell")
with this:
collectionView!.registerClass(RDCell.self, forCellWithReuseIdentifier: "Cell")
If your cell class name is different from mine (not RDCell
), then wherever I write RDCell
you write the name of your cell class.
As you can see, we didn’t import anything, unlike in Objective-C. This is because here in Swift every file knows about any class in the project, so the only one thing that you import
is frameworks like UIKit
or QuartzCore
.
We need to do one little thing before we begin working with the cell. Replace this piece of code
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("Cell", forIndexPath: indexPath)
with this:
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("Cell", forIndexPath: indexPath) as! RDCell
So now the cell variable is of type RDCell, not UICollectionViewCell. This is important, because now we can get access to RDCell’s properties and methods.
The Cell
Open RDCell.swift
file (in my case. In yours it’s the file in which you have your new cell class).
Here we’ll do all the magic. Because this article is about simple cells, we’ll create a cell with just a label and an image view, to tell you how to put pictures and text to a cell.
So we need a UILabel
and a UIImageView
objects to add to our cell. Add two new variables to RDCell
class.
var textLabel: UILabel! var imageView: UIImageView!
Alright. Let’s initialize them.
override init(frame: CGRect) { super.init(frame: frame) imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: frame.size.width, height: frame.size.height*2/3)) imageView.contentMode = UIViewContentMode.ScaleAspectFit contentView.addSubview(imageView) textLabel = UILabel(frame: CGRect(x: 0, y: imageView.frame.size.height, width: frame.size.width, height: frame.size.height/3)) textLabel.font = UIFont.systemFontOfSize(UIFont.smallSystemFontSize()) textLabel.textAlignment = .Center contentView.addSubview(textLabel) }
If you take a look at the frames of image view and label, you’ll see that the image view will take 2/3 of the whole space in the cell. And label will take the rest 1/3. We’ve set imageView.contentMode
property to .ScaleAspectFit
, so any image that you pass to image view will be scaled to fit the image view frame, and an image’s aspect ratio will be saved.
Ok, we’re done but here we have some errors. Let’s fix ’em.
In Xcode 6 you have to provide additional init(coder:)
initializer in classes like RDCell
, which is the subclass of UICollectionViewCell
. This initializer is called instead of init(frame:)
when the class gets initialized from a storyboard or a xib file. That’s not our case, but we still need to provide init(coder:)
. We can use the solution provided to us by Xcode. In Issue Navigator click on an error that says “'required' initializer 'init(coder:)' must be provided by subclass of 'UICollectionViewCell'
“, and then press Return or double-click on a “Fix-it Insert …” row that will appear. You can also click on a red circle with a white dot on a vertical bar to the left of your code where line numbers are, and do the same thing.
Ok, it automatically pasted to our class this piece of code:
required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") }
Well, “Fatal error” sounds creepy. But nothing to worry about, because this initializer in our case never going to be called.
Exclamation marks in our variables declarations after a class name means that this object is implicitly unwrapped optional. This is pretty dangerous, because if you try to work with an object before it’s initialized, your app will crash. So why are we doing that here?
The thing is, in Swift all non-optional variables must be initialized before calling superclass’ initializer. If you place an exclamation mark after a class name, Swift will think that this variable is already initialized, and no error will occur. So I did it to just simplify our code in init(frame:)
. If you want, you can remove exclamation marks, and then you need to place text label and image view initialization code before super.init(frame: frame)
, like this:
override init(frame: CGRect) { imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: frame.size.width, height: frame.size.height*2/3)) textLabel = UILabel(frame: CGRect(x: 0, y: imageView.frame.size.height, width: frame.size.width, height: frame.size.height/3)) super.init(frame: frame) // ... and then the rest of the code }
To continue, you’ll need an image to set to our cell imageView.image
. You can use mine. It needs to be named "star@2x.png"
. After you download the image, drag and drop it to the Xcode project and check Copy items if needed, then click Finish.
Test
Alright, so now we have ready-to-use cell, let’s set some text to it and the image so we’ll see the result.
Go to ViewController.swift
and edit collectionView(_:cellForItemAtIndexPath:)
method:
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("Cell", forIndexPath: indexPath) as! RDCell cell.textLabel.text = "Text" cell.imageView.image = UIImage(named: "star") return cell
That’s it. Run the app by pressing ⌘R and you’ll see the same number of cells but with a star and a text in it.
So this is how you create a simple UICollectionViewCell
and use it. Don’t forget to experiment with it! For example, try to create an array of strings and/or image names and pass each one for each cell.
UICollectionViewCell with XIB
If you want, you can create cells in the Interface Builder. When you add new UICollectionViewCell
subclass, check Also create XIB file and Xcode will create a xib file for you.
If you already created a UICollectionViewCell
subclass and you want to create a xib file for it:
1. Select File — New — File… or press ⌘N, select User Interface under iOS on the left, then select View and click Next. Give that file the same name as your cell class (doesn’t matter though, it’s just to keep things clean).
2. Go to this new xib file and delete the view that’s here.
3. Drag and drop a Collection View Cell from the bottom of the Utilities panel (⌥⌘0).
4. Select this Collection View Cell and switch to Identity inspector on the Utilities panel (⌥⌘3).
5. In Custom Class section in Class field write your custom cell class name (RDCell
in my case).
6. Now remove the line where you register your class as a cell and register the nib instead. For example, in our example above you need to replace this line of code:
collectionView!.registerClass(RDCell.self, forCellWithReuseIdentifier: "Cell")
with this:
collectionView!.registerNib(UINib(nibName: "RDCell", bundle: nil), forCellWithReuseIdentifier: "Cell")
Now that’s it. Remember that now you need to recreate our imageView
and textLabel
in Interface Builder.
The init(frame:)
method will not get called now, instead init(coder:)
will be called. So if you followed this tutorial, you need to make a change to init(coder:)
:
required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) }
Now it won’t crash with the error “init(coder:) has not been implemented
“, because now you actually implemented it.
Write @IBOutlet
at the beginning of a variable declaration to make it available in the Interface Builder. An IBOutlet
object must not be a constant. This means, not let
, but var
.
@IBOutlet let textLabel: UILabel!
— compiler will be angry if you write this.
All IBOutlets
will be available from the Connections Inspector on the Utilities panel, when you select your Collection View Cell.
And finally, if you use xib file and you still need to do some initialization in code, override awakeFromNib()
method.
Where to go from here?
Check out the documentation of course. It’s not really rich though, because it’s only up to you what will be in the cell and what it’ll do. Also, take a look at Advanced User Interfaces with Collection Views video from WWDC 2014, it may be useful for you.
If you have a question, suggestion, or if I did something wrong here, please let me now in comments below.
thank you for your time and the tutorial. I appreciate it.
You did a great job, very simple and clear … Thank u so much
Well, I tried everything to set individual text labels using an array.
Not so easy when you don’t know how?
The trick is in
indexPath
parameter incollectionView(_:cellForItemAtIndexPath:)
method. I’ll write a post about it, so come back a little later if you don’t figure it out.Finally, got the indexPath.row syntax.
Why is finding simple bits of code made so difficult?
It’s not, you’ll get the hang of it.
Thanks for your help.
You’re welcome.
I enjoyed this tutorial and topic. I extended the idea to use relative values, so that the layout works across different devices. In addition to creating individual labels, and rather than create and upload 14 (15 in my case) image files, I also use an array with a for in loop to set different cell background colours. I give the labels a semi-opaque background colour to ensure black label text is readable across a range of available standard UIColors (including black). I did however, make my own seasonal star.
Thanks again.
-u
P.s. Here is link to a screengrab:
https://www.dropbox.com/s/wmc704sdy4ne2ih/Screen%20Shot%202014-11-22%20at%2022.40.01.png?dl=0
Glad to see people experimenting 🙂
Yes, although my code will work across devices, its content does not rotate.
Hi there, I loved the tutorial! I was wondering if you would consider taking it a bit further and say showing how to link it up to display a viewcontroller when different items are selected?
Thank you, I’ll think about this.
I just want to say thank you! Not only did you help me better understand how to use collections you did it in a way that I felt like I learned the little things along the way. There are tons of tutorials out there that just have you copy in code and for someone who has been coding for a while that may be enough to understand how things work but for someone like me who is new you explain things really well. I just wanted to say thank you and you are awesome sir.
Thank you! I’m glad it helped.
Hey thanks a bunch for this article, very well written and it was exactly what I needed after searching for 2 hours. Keep up the great work, I’ll be bookmarking this site for sure!
Hi, I did the exact same process and now am facing a strange problem.
I tried to explain in this thread. Would you please help?
http://stackoverflow.com/questions/29655552/scrolling-uicollectionview-uses-lots-of-memory-leading-to-crash
Hi! For me I run into this problem: Initializer does not override a designated initializer from its superclass. And additionally, it says I did not initialize textLabel and imageLabel…
Nice article. 🙂 Do you really need to call the cell something like ‘RDCell’, though? I thought we didn’t need these two-character prefixes any more, since Swift has namespacing.
This and the tutorial about programmatic collection views were both well put together and surprisingly easier to comprehend for me than the other CollectionView tutorials I’ve seen that use IB.
Hey could you add this on github? It would really help having the whole thing in front of me.
Thanks for the great tutorial!
In this case all my cells will have the same image and label. How can I set each cell separately?
Hi there, so I have a strange problem. In the collectionView(_:cellForItemAtIndexPath:) method I can change the background color of each cell, but for some reason I can only set an image for the first cell in the collection view. For the others, the same code runs (cell.imageView!.image = img), but no image gets set. I can say for sure that the img variable is a real image in every case as I tried it with UIImage(named: “something static”).
Any ideas?
Thanks, it’s a great tutorial but I’m not sure what I missed.
Thank you very much, I saved lots of time by going through this.
I don’t know how to thank you enough. I’ve spent 20 hours trying to look through Apple documentation and stackoverflow…and everything was way too advance.
Your teaching style is patient and thorough, and repeats concepts from earlier, which helps me learn.