How to make a simple collection view with Swift
Asked Answered
C

5

205

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.

Carotid answered 30/7, 2015 at 22:36 Comment(0)
C
578

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

Carotid answered 30/7, 2015 at 22:36 Comment(30)
im getting cell.myLabel as nil and it's crashing. any idea why? Im using a custom layoutAnticholinergic
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.Carotid
if you are using interface builder and reference the cell using an outlet, do not register the custom cell class in your controller. see thisAnticholinergic
@Carotid 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 connectionTmesis
@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.Carotid
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 storyboardTrapper
I wish Apple's documentation was as easy to read as this explanationBrasilin
Remember that the default backgroundColor of UICollectionView is black.Orcutt
@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.Carotid
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.Tosch
@IlyaVinogradov, I've never done this and a survey of other SO questions seems to indicate that it is not possible (see here and here).Carotid
@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?Prejudice
@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.Carotid
Thanks @Carotid > #44261736Prejudice
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?Scutellation
@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.Carotid
@Carotid 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.Scutellation
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?Arduous
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)?Arduous
@GeorgeKendros, that is correct. Those are the mandatory methods for the data source and delegate protocols. It tells them apart by the parameters.Carotid
@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.Carotid
@Carotid 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 burnoutPhosphorism
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...Roadability
Swift 5: cell was not working as an identifier. It worked after giving it a different identifier.Salsbury
I don't have any cell underneath my collectionview in the storyboard, and no way to add one. Anyone knows how to?Coralyn
@Coralyn attribute inspector first option is "Items". Are you sure you dragged a collection view and not something else?Shurwood
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.Marcello
'cell.myLabel.text = self.items[indexPath.item]' should be 'cell.myLabel.text = self.items[indexPath.row]'Protuberancy
Can I just say thank you @AlexZavatone for this really simple guide. As a noob this has saved my ass each time I forget an element of the process to set a collection view upOdontology
Remember to add these lines to your controller as well in order to register the cell: let nib = UINib.init(nibName: "MyCustomCell", bundle: nil) self.tblUsers.register(nib, forCellReuseIdentifier: "MyCustomCell")Collocation
V
5

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
}
Vaughn answered 6/11, 2018 at 9:7 Comment(0)
G
4

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
}
Gherlein answered 22/6, 2017 at 9:46 Comment(0)
R
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)
    }

}
Ridenhour answered 4/9, 2018 at 18:38 Comment(0)
T
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.

Twosided answered 16/12, 2017 at 19:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.