197

I'm trying to learn how to use UICollectionView. The documentation is a little hard to understand and the tutorials that I found were either in Objective C or long complicated projects.

When I was learning how to use UITableView, We ❤ Swift's How to make a simple tableview with iOS 8 and Swift had a very basic setup and explanation to get me going. Is there anything like this for UICollectionView?

The answer below is my attempt to learn to do this.

Suragch
  • 364,799
  • 232
  • 1,155
  • 1,198

5 Answers5

537

This project has been tested with Xcode 10 and Swift 4.2.

Create a new project

It can be just a Single View App.

Add the code

Create a new Cocoa Touch Class file (File > New > File... > iOS > Cocoa Touch Class). Name it MyCollectionViewCell. This class will hold the outlets for the views that you add to your cell in the storyboard.

import UIKit
class MyCollectionViewCell: UICollectionViewCell {
    
    @IBOutlet weak var myLabel: UILabel!
}

We will connect this outlet later.

Open ViewController.swift and make sure you have the following content:

import UIKit
class ViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate {
    
    let reuseIdentifier = "cell" // also enter this string as the cell identifier in the storyboard
    var items = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31", "32", "33", "34", "35", "36", "37", "38", "39", "40", "41", "42", "43", "44", "45", "46", "47", "48"]
    
    
    // MARK: - UICollectionViewDataSource protocol
    
    // tell the collection view how many cells to make
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return self.items.count
    }
    
    // make a cell for each cell index path
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        
        // get a reference to our storyboard cell
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath as IndexPath) as! MyCollectionViewCell
        
        // Use the outlet in our custom class to get a reference to the UILabel in the cell
        cell.myLabel.text = self.items[indexPath.row] // The row value is the same as the index of the desired text within the array.
        cell.backgroundColor = UIColor.cyan // make cell more visible in our example project
        
        return cell
    }
    
    // MARK: - UICollectionViewDelegate protocol
    
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        // handle tap events
        print("You selected cell #\(indexPath.item)!")
    }
}

Notes

  • UICollectionViewDataSource and UICollectionViewDelegate are the protocols that the collection view follows. You could also add the UICollectionViewFlowLayout protocol to change the size of the views programmatically, but it isn't necessary.
  • We are just putting simple strings in our grid, but you could certainly do images later.

Set up the storyboard

Drag a Collection View to the View Controller in your storyboard. You can add constraints to make it fill the parent view if you like.

enter image description here

Make sure that your defaults in the Attribute Inspector are also

  • Items: 1
  • Layout: Flow

The little box in the top left of the Collection View is a Collection View Cell. We will use it as our prototype cell. Drag a Label into the cell and center it. You can resize the cell borders and add constraints to center the Label if you like.

enter image description here

Write "cell" (without quotes) in the Identifier box of the Attributes Inspector for the Collection View Cell. Note that this is the same value as let reuseIdentifier = "cell" in ViewController.swift.

enter image description here

And in the Identity Inspector for the cell, set the class name to MyCollectionViewCell, our custom class that we made.

enter image description here

Hook up the outlets

  • Hook the Label in the collection cell to myLabel in the MyCollectionViewCell class. (You can Control-drag.)
  • Hook the Collection View delegate and dataSource to the View Controller. (Right click Collection View in the Document Outline. Then click and drag the plus arrow up to the View Controller.)

enter image description here

Finished

Here is what it looks like after adding constraints to center the Label in the cell and pinning the Collection View to the walls of the parent.

enter image description here

Making Improvements

The example above works but it is rather ugly. Here are a few things you can play with:

Background color

In the Interface Builder, go to your Collection View > Attributes Inspector > View > Background.

Cell spacing

Changing the minimum spacing between cells to a smaller value makes it look better. In the Interface Builder, go to your Collection View > Size Inspector > Min Spacing and make the values smaller. "For cells" is the horizontal distance and "For lines" is the vertical distance.

Cell shape

If you want rounded corners, a border, and the like, you can play around with the cell layer. Here is some sample code. You would put it directly after cell.backgroundColor = UIColor.cyan in code above.

cell.layer.borderColor = UIColor.black.cgColor
cell.layer.borderWidth = 1
cell.layer.cornerRadius = 8

See this answer for other things you can do with the layer (shadow, for example).

Changing the color when tapped

It makes for a better user experience when the cells respond visually to taps. One way to achieve this is to change the background color while the cell is being touched. To do that, add the following two methods to your ViewController class:

// change background color when user touches cell
func collectionView(_ collectionView: UICollectionView, didHighlightItemAt indexPath: IndexPath) {
    let cell = collectionView.cellForItem(at: indexPath)
    cell?.backgroundColor = UIColor.red
}

// change background color back when user releases touch
func collectionView(_ collectionView: UICollectionView, didUnhighlightItemAt indexPath: IndexPath) {
    let cell = collectionView.cellForItem(at: indexPath)
    cell?.backgroundColor = UIColor.cyan
}

Here is the updated look:

enter image description here

Further study

UITableView version of this Q&A

Alex Zavatone
  • 3,522
  • 32
  • 49
Suragch
  • 364,799
  • 232
  • 1,155
  • 1,198
  • 1
    im getting cell.myLabel as nil and it's crashing. any idea why? Im using a custom layout – Gerald Feb 06 '16 at 09:09
  • 4
    If you didn't hook up the outlet by control-dragging from the label in the storyboard to the `@IBOutlet` for `myLabel` in code, you would get a crash like this. – Suragch Feb 06 '16 at 09:22
  • 3
    if you are using interface builder and reference the cell using an outlet, do not register the custom cell class in your controller. see [this](http://stackoverflow.com/questions/35239543/nil-label-for-custom-uicollectioncell) – Gerald Feb 06 '16 at 13:40
  • @Suragch you say: "Hook the Label in the collection cell to myLabel in the MyCollectionViewCell class" how do I add that as a hook up to another class? I am working on the ViewController and I cannot see where to drag the connection – JamesG Aug 26 '16 at 12:49
  • @JamesG, Don't use the View Controller. Select the `MyCollectionViewCell` class code. Control drag from the label in the Interface builder to the `myLabel` outlet in the code. – Suragch Aug 26 '16 at 15:08
  • 2
    if UICollectionViewCell outlet is nil then you have to remove `self.collectionView.registerClass(MyCollectionViewCell.self, forCellWithReuseIdentifier: "Cell")`. If you still have the problem check wether `reuseIdentifier` is same in `dequeueReusableCellWithReuseIdentifier` and in storyboard – Ashok R Sep 11 '16 at 22:00
  • 43
    I wish Apple's documentation was as easy to read as this explanation – elmarko Sep 25 '16 at 20:43
  • Remember that the default `backgroundColor` of `UICollectionView` is black. – Annabelle Sykes Oct 28 '16 at 17:03
  • @AnnabelleSykes, the default background color for me used to be black when I was making this project in Xcode 7, but most recently when I retested the project in Xcode 8, the default was white. Anyway, whatever the default is for you, it can be changed in the Interface Builder or in code. – Suragch Oct 29 '16 at 00:17
  • Is it possible to build a static collection view entirely in the Storyboard, similar to how you can build a table view with static content? With static content table view you can completely avoid using cellForRowAtIndexPath:, I was wondering it it's possible to get away without implementing cellForItemAtIndexPath: for my collection view. – Ilya Vinogradov Oct 30 '16 at 04:13
  • 1
    @IlyaVinogradov, I've never done this and a survey of other SO questions seems to indicate that it is not possible (see [here](http://stackoverflow.com/q/28524691) and [here](http://stackoverflow.com/q/24341833)). – Suragch Oct 30 '16 at 06:45
  • @suragch thank you so much for this! Question - I have a function dynamically creating the data array... how can I call reloadData() for the uicollectionview from my data function? – barrylachapelle May 30 '17 at 06:04
  • @andehlu, I haven't been working with this for a while so I can't remember very well. Off hand I would say load the data in a background thread and when it completes then update the UI with `reloadData()`. It that doesn't work try asking a new question. – Suragch May 30 '17 at 10:10
  • Thanks @Suragch > https://stackoverflow.com/questions/44261236/reloading-uicollection-view-after-data-parse – barrylachapelle May 30 '17 at 11:38
  • Cells will repeat if we add more than visible cells. E.g., if I add 1000 in array and try to load in the collection view. While scrolling the collection view, the cells will be repeated. I know its because of reusable identifier, if we want to keep dynamic reuse identifier, then we need to register all reuse identifiers in viewDidLoad and also we have to separate customer cell XIB from storyboard. That look little odd and I feel its not a standard. Do we have any alternate to that solution? – Srinivas G Jun 10 '17 at 10:13
  • @SrinivasG, You mean that you made the `items` array go from 1 to 1000 (`["1", "2", ... "999", "1000"]`) but it repeated the first screen of numbers (1~70 or so)? I'll make a note to retest this, but if you want a quicker answer you might ask a new question on Stack Overflow. – Suragch Jun 10 '17 at 12:36
  • @Suragch Thanks for checking it out. Yes, the cells will be repeating if custom cell of collection view embedded in storyboard. We were able to get it working properly if the custom cell separated from storyboard as new UICollectionViewCell xib and register dynamic reuse identifiers in viewDidLoad, but that looks odd in registering all identifiers. – Srinivas G Jun 12 '17 at 05:54
  • Thanks for this. Its a great tutorial. It's working perfectly but I'm a tiny bit confused on how it works? The same function "func collectionView" exists 3 times. How does it know which function to run? – George Kendros Nov 17 '17 at 11:48
  • Sorry, I was looking at it more and think I get it now. When you, for example, link the collection views data source to the file then it looks in there and expects to find at least its mandatory methods (which it tells apart by the parameters)? – George Kendros Nov 17 '17 at 12:01
  • @GeorgeKendros, that is correct. Those are the mandatory methods for the data source and delegate protocols. It tells them apart by the parameters. – Suragch Nov 17 '17 at 13:49
  • @SrinivasG, I finally got around to testing this. I made the array `["1", "2", ... "99", "100"]` so that it scrolled off screen. Everything seemed to work fine. The cells didn't repeat for me. I didn't change anything else about the project besides the data array. – Suragch Dec 28 '17 at 10:29
  • @Suragch please help me... I really don't get how, but on first attempt I was able to ctrl-drag the label because I had the class in the "Automatic" section of the "Assitant Editor" but now I only got the viewcontroller class I'm having a burnout – Manu Nov 02 '18 at 10:45
  • Don't forget to attach your dataSource and delegate (apologies if this has already been mentioned) - this was the last thing that I hadn't done... – Pixel May 09 '19 at 11:14
  • Swift 5: `cell` was not working as an identifier. It worked after giving it a different identifier. – busterroni Sep 06 '19 at 20:48
  • I don't have any cell underneath my collectionview in the storyboard, and no way to add one. Anyone knows how to? – agirault Nov 22 '19 at 22:07
  • @agirault attribute inspector first option is "Items". Are you sure you dragged a collection view and not something else? – Daniel Springer Mar 13 '20 at 13:55
  • how did you drag drop the label to the UIViewCell Class. My assistant editor doesn't open the UIViewCell class, but always opens the ViewController class that has the Collection. – thenakulchawla Mar 24 '20 at 04:39
  • 'cell.myLabel.text = self.items[indexPath.item]' should be 'cell.myLabel.text = self.items[indexPath.row]' – Alex Zavatone Sep 08 '20 at 21:36
4

For swift 4.2 --

//MARK: UICollectionViewDataSource

func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
    return 1     //return number of sections in collection view
}

func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    return 10    //return number of rows in section
}

func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "collectionCell", for: indexPath as IndexPath)
    configureCell(cell: cell, forItemAtIndexPath: indexPath)
    return cell      //return your cell
}

func configureCell(cell: UICollectionViewCell, forItemAtIndexPath: NSIndexPath) {
    cell.backgroundColor = UIColor.black


    //Customise your cell

}

func collectionView(collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, atIndexPath indexPath: NSIndexPath) -> UICollectionReusableView {
    let view =  collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionElementKindSectionHeader, withReuseIdentifier: "collectionCell", for: indexPath as IndexPath) as UICollectionReusableView
    return view
}

//MARK: UICollectionViewDelegate
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
    // When user selects the cell
}

func collectionView(collectionView: UICollectionView, didDeselectItemAtIndexPath indexPath: NSIndexPath) {
    // When user deselects the cell
}
Abhishek Mishra
  • 1,547
  • 13
  • 30
3

Delegates and Datasources of UICollectionView

//MARK: UICollectionViewDataSource

override func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
    return 1     //return number of sections in collection view
}

override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    return 10    //return number of rows in section
}

override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCellWithReuseIdentifier("collectionCell", forIndexPath: indexPath)
    configureCell(cell, forItemAtIndexPath: indexPath)
    return cell      //return your cell
}

func configureCell(cell: UICollectionViewCell, forItemAtIndexPath: NSIndexPath) {
    cell.backgroundColor = UIColor.blackColor()


    //Customise your cell

}

override func collectionView(collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, atIndexPath indexPath: NSIndexPath) -> UICollectionReusableView {
    let view =  collectionView.dequeueReusableSupplementaryViewOfKind(UICollectionElementKindSectionHeader, withReuseIdentifier: "collectionCell", forIndexPath: indexPath) as UICollectionReusableView
    return view
}

//MARK: UICollectionViewDelegate
override func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
      // When user selects the cell
}

override func collectionView(collectionView: UICollectionView, didDeselectItemAtIndexPath indexPath: NSIndexPath) {
     // When user deselects the cell
}
Saranjith
  • 9,547
  • 2
  • 55
  • 102
2

UICollectionView implementation is quite interesting. You can use the simple source code and watch a video tutorial using these links :

https://github.com/Ady901/Demo02CollectionView.git

https://www.youtube.com/watch?v=5SrgvZF67Yw

extension ViewController : UICollectionViewDataSource {

    func numberOfSections(in collectionView: UICollectionView) -> Int {
        return 2
    }

    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return nameArr.count
    }

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "DummyCollectionCell", for: indexPath) as! DummyCollectionCell
        cell.titleLabel.text = nameArr[indexPath.row]
        cell.userImageView.backgroundColor = .blue
        return cell
    }

}

extension ViewController : UICollectionViewDelegate {

    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        let alert = UIAlertController(title: "Hi", message: "\(nameArr[indexPath.row])", preferredStyle: .alert)
        let action = UIAlertAction(title: "OK", style: .default, handler: nil)
        alert.addAction(action)
        self.present(alert, animated: true, completion: nil)
    }

}
Ryan
  • 8,298
  • 9
  • 42
  • 53
Aditya Sharma
  • 485
  • 4
  • 18
0

UICollectionView is same as UITableView but it gives us the additional functionality of simply creating a grid view, which is a bit problematic in UITableView. It will be a very long post I mention a link from where you will get everything in simple steps.

Dilraj Singh
  • 600
  • 7
  • 9