73

I tried many days to realise this: enter image description here

I want to add in my UIViewController two different CollectionView. For example I want to put images in these collectionView Each CollectionView use its own images. Is this possible?

I will be very happy if somebody can give me a hand. :)

craft
  • 2,017
  • 1
  • 21
  • 30
Masterfego
  • 2,648
  • 1
  • 26
  • 32

6 Answers6

154

This is possible, you just need to add each UICollectionView as a subview, and set the delegate and dataSource to your UIViewController.

Here's a quick example. Assuming you have one UICollectionView working, you should be able to adapt this code to your own uses to add a second fairly easily:

let collectionViewA = UICollectionView()
let collectionViewB = UICollectionView()
let collectionViewAIdentifier = "CollectionViewACell"
let collectionViewBIdentifier = "CollectionViewBCell"

override func viewDidLoad() {
    // Initialize the collection views, set the desired frames
    collectionViewA.delegate = self
    collectionViewB.delegate = self

    collectionViewA.dataSource = self
    collectionViewB.dataSource = self

    self.view.addSubview(collectionViewA)
    self.view.addSubview(collectionViewB)
}

In the cellForItemAtIndexPath delegate function:

func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
    if collectionView == self.collectionViewA {
        let cellA = collectionView.dequeueReusableCellWithReuseIdentifier(collectionViewAIdentifier) as UICollectionViewCell

        // Set up cell
        return cellA
    }

    else {
        let cellB = collectionView.dequeueReusableCellWithReuseIdentifier(collectionViewBIdentifier) as UICollectionViewCell

        // ...Set up cell

        return cellB
    }
}

In the numberOfItemsInSection function:

func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    if collectionView == self.collectionViewA {
        return 0 // Replace with count of your data for collectionViewA
    }

    return 0 // Replace with count of your data for collectionViewB
}
Sam
  • 4,994
  • 4
  • 30
  • 37
  • I get this error "UICollectionView must be initialized with a non-nil layout parameter" on this line "let collectionViewA = UICollectionView()" – dennis Jan 27 '17 at 16:29
  • 3
    it's not worked for me! i got this error: *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'UICollectionView must be initialized with a non-nil layout parameter' – Mohammad Mirzakhani Nov 29 '17 at 06:34
  • 5
    i think this "if collectionView == self.collectionViewA" this is too heavy of comparison (you might inherit this habit in other code, which is bad). I would suggest to use tag property. Which is basically its purpose – Andrew James Ramirez Apr 24 '18 at 03:38
  • I am getting an error, stating "Missing return in a function expected to return 'UICollectionViewCell'". I am using two return statements in if and else blocks. – Neck May 09 '18 at 04:26
  • @Neck updated the answer. This will work. Just add "return UICollectionView()" after the if-else block – Deepak Terse May 28 '18 at 12:43
  • @AndrewJamesRamirez Good point, I've created enum and used tags, ty – えるまる Aug 13 '18 at 08:41
  • you are a hero! – Axel López Mar 15 '19 at 16:36
  • I think you are missing this in viewDidLoad:
    self.collectionView.register(Cell.self, forCellWithReuseIdentifier: Cell.identifier)
    (Variable names will have to be changed.)
    – Parth Aug 29 '19 at 21:10
  • the problem with this is that delegate will only be called on the first view – droid Mar 17 '20 at 13:43
12

Yes--this is entirely possible. You can either assign their respective UICollectionViewDelegates/UICollectionViewDataSources to different classes or subclass the CollectionViews, assigning both the delegate and data source to your current viewController and downcast your reference to collectionView in the delegation methods like so:

@IBOutlet collectionViewA: CustomCollectionViewA!
@IBOutlet collectionViewB: CustomCollectionViewB!


func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {

    if let a = collectionView as? CustomCollectionViewA {
        return a.dequeueReusableCellWithIdentifier("reuseIdentifierA", forIndexPath: indexPath)
    } else {
        return collectionView.dequeueReusableCellWithIdentifier("reuseIdentifierB", forIndexPath: indexPath)    
    }
}

Subclass UICollectionView like this:

class CustomCollectionViewA: UICollectionView {
    // add more subclass code as needed
}

class CustomCollectionViewB: UICollectionView {
    // add more subclass code as needed
}
craft
  • 2,017
  • 1
  • 21
  • 30
kellanburket
  • 12,250
  • 3
  • 46
  • 73
  • 1
    What do you mean by "subclass the CollectionViews"? I did not manage to do what you are saying. – Damasio Nov 27 '15 at 23:48
9

You can use the factory design pattern to build two different collection views and return them via functions. Here's my working version for swift 4.

This code goes in a separate helper file:

import UIKit

class collectionViews {

static func collectionViewOne() -> UICollectionView {

    let layout = UICollectionViewFlowLayout()
    let collectionViewOne = UICollectionView(frame: CGRect(x: 0, y: 20, width: 200, height: 100), collectionViewLayout: layout)
    return collectionViewOne

}

static func collectionViewTwo() -> UICollectionView {

    let layout = UICollectionViewFlowLayout()
    let collectionViewTwo = UICollectionView(frame: CGRect(x: 0, y: 300, width: 200, height: 100), collectionViewLayout: layout)
    return collectionViewTwo

}


}

And here is the view controller code:

import UIKit

class ViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate {


let collectionViewOne = collectionViews.collectionViewOne()
let collectionViewTwo = collectionViews.collectionViewTwo()

var myArray = ["1", "2"]
var myArray2 = ["3", "4"]

override func viewDidLoad() {
    super.viewDidLoad()


    collectionViewOne.delegate = self
    collectionViewOne.dataSource = self
    collectionViewOne.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "MyCell")
    view.addSubview(collectionViewOne)


    collectionViewTwo.delegate = self
    collectionViewTwo.dataSource = self
    collectionViewTwo.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "MyCell2")
    view.addSubview(collectionViewTwo)

}

func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {

    if collectionView == self.collectionViewOne {
        return myArray.count
    } else {
        return myArray2.count
    }

}

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {

    if collectionView == self.collectionViewOne {
        let myCell = collectionView.dequeueReusableCell(withReuseIdentifier: "MyCell", for: indexPath as IndexPath)

        myCell.backgroundColor = UIColor.red

        return myCell

    } else {

        let myCell2 = collectionView.dequeueReusableCell(withReuseIdentifier: "MyCell2", for: indexPath as IndexPath)

        myCell2.backgroundColor = UIColor.blue

        return myCell2
    }

}


}

Result

craft
  • 2,017
  • 1
  • 21
  • 30
brontea
  • 555
  • 4
  • 14
  • Will this scroll and can you use "func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath)" with this? – B-Tron of the Autobots Mar 07 '19 at 21:59
  • Yes I used it in an app I'm working on and it scrolls/selects fine - when you implement `didSelectItemAt` just make sure you use the if/else statement to set the right action for each collection view. – brontea Mar 08 '19 at 01:42
  • Why is this displaying only one item? – AG_HIHI Sep 17 '20 at 15:50
8

You can also name the collection views outlets differently (without subclassing):

@IBOutlet weak var collectionView: UICollectionView!

@IBOutlet weak var SecondCollectioView: UICollectionView!

method:

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "customCell", for: indexPath) as UICollectionViewCell

    if(collectionView == self.SecondCollectioView) {
        cell.backgroundColor = UIColor.black
    } else {
         cell.backgroundColor = self.randomColor()
    }

    return cell;
}

This is will be an another way.

craft
  • 2,017
  • 1
  • 21
  • 30
GvSharma
  • 2,632
  • 1
  • 24
  • 30
2

Here's my working version for swift 5 and Xcode 11:

create outlets for corresponding collectionviews: outlets:

@IBOutlet weak var bgCollectionView: UICollectionView!
@IBOutlet weak var frontCollectionView: UICollectionView!
var arrImages = [String : [UIImage]]()

arrImages is contain like

override func viewDidLoad() {
        super.viewDidLoad()
    arrImages = [
    "frontImg": [//Front UIImage array],
    "bgImg": [//Background UIImage array]
    ]
}

 func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        if let arrImg = arrImages["bgImg"] {
            return arrImg.count
        } else if let arrImg = arrImages["frontImg"]{
            return arrImg.count
        }
        return 0
    }

You can do this two ways

  1. Using CollectionView Outlets

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! CollectionViewCell    
    if collectionView == self.bgCollectionView{
            if let arrImg = arrImages["bgImg"]{
                cell.imgView.image = arrImg[indexPath.row]
            }
        }else{
            if let arrImg = arrImages["frontImg"]{
                cell.imgView.image = arrImg[indexPath.row]
            }
        }
        return cell
    }
  1. Using CollectionView Tag: Here Background Images collectionview tag is 1 and Front Images collectionview tag is 2.

    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! CollectionViewCell
        if collectionView == collectionView.viewWithTag(1){
            if let arrImg = arrImages["bgImg"]{
                cell.imgView.image = arrImg[indexPath.row]
            }
        }else{
            if let arrImg = arrImages["frontImg"]{
                cell.imgView.image = arrImg[indexPath.row]
                }
            }
            return cell
        }
     

Please Add Tag in CollectionView Like this: enter image description here

Thank You. Hope It's working for you !!

Yogesh Patel
  • 1,893
  • 1
  • 20
  • 55
0

Swift 5 Answer!

If you try connecting both collectionViews to the same view controller Xcode will throw an error "Outlets cannot connect to repeating content"

Solution:

Head to Storyboard

  1. Connect the first collectionView via outlet, set the delegate/dataSource in viewDidLoad and then add a tag to the second collectionView by heading to the attributes inspector in storyboard and change the value from 0 to 1

  2. Select the secondCollectionView and go to the connections inspector and select delegate and drag the connection to the UIViewController and the same for the dataSource.

  3. Simply check which collectionView is passing through.

     func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    
         if collectionView == collectionView.viewWithTag(1) {
    
            let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "secondCollectionView", for: indexPath)
    
            return cell
          }
    
         else {
    
            let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "firstCollectionView", for: indexPath) as! HomeMainCollectionViewCell
    
            cell.configureCell()
    
            return cell}
         }