Apple Apple - 2 months ago 10
Swift Question

How to call collectionView.reloadData() from reusable header button?

I have a button in my reusable header view that deletes information in the array that supplies the data for the collection view and header. I want it to also be able to perform

collectionView.reloadData()
.

The issue is that I can't call
collectionView.reloadData()
from the header button because it doesn't recognize collectionView as a variable. If I call
Builds().collectionView.reloadData()
(Builds is the View controller) the app crashes because it says that it found nil while unwrapping optional. I know that simply calling
collectionView.reloadData()
isn't the problem because i have
collectionView.reloadData()
called in
viewDidAppear()
and that gives me no crashes.

Whats going on here? How can I get my
collectionView
to reload data after the button removes the data?

For reference:

Reusable Header:

import UIKit

class BuildsHeader: UICollectionReusableView {

@IBOutlet weak var headerLabel: UILabel!

@IBOutlet weak var deleteBuildButton: UIButton!

@IBAction func deleteBuildButton(sender: UIButton) {

for hero in heroes{ if hero.hero.name == heroForDetails.hero.name {
hero.builds.removeAtIndex(sender.tag)
heroForDetails = hero
}}

saveToDefaults(userProfile)
//Builds().collectionBuild.reloadData() runs just fine without this line
}
}


ViewController:

import UIKit

class Builds: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {

@IBOutlet weak var collectionBuild: UICollectionView!

override func viewDidLoad() {
super.viewDidLoad()

collectionBuild.delegate = self
collectionBuild.dataSource = self

}

override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}

override func viewDidAppear(animated: Bool) {
collectionBuild.reloadData()
}

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

if let cell = collectionView.dequeueReusableCellWithReuseIdentifier("BuildCell", forIndexPath: indexPath) as? TalentsCell {

let build = heroForDetails.builds[indexPath.section]

switch (indexPath.row) {

case 0:
cell.configureCell(build["1"]! as! Talent)
case 1:
cell.configureCell(build["4"]! as! Talent)
case 2:
cell.configureCell(build["7"]! as! Talent)
case 3:
cell.configureCell(build["10"]! as! Talent)
case 4:
cell.configureCell(build["13"]! as! Talent)
case 5:
cell.configureCell(build["16"]! as! Talent)
case 6:
cell.configureCell(build["20"]! as! Talent)
default:
cell.configureCell(build["1"]! as! Talent)

}

return cell

}
return UICollectionViewCell()
}

func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {

}

func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 7
}

func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return heroForDetails.builds.count
}

func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
return CGSizeMake(50,50)
}

func collectionView(collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, atIndexPath indexPath: NSIndexPath) -> UICollectionReusableView {

let header = collectionView.dequeueReusableSupplementaryViewOfKind(UICollectionElementKindSectionHeader, withReuseIdentifier: "BuildsHeader", forIndexPath: indexPath) as! BuildsHeader

var buildDict = heroForDetails.builds[indexPath.section]
header.headerLabel.text = buildDict["name"] as! String
header.deleteBuildButton.tag = indexPath.section
return header

}
}


enter image description here

Answer

You can achieve that with delegation.

  1. Create a protocol
  2. Create a variable inside BuildsHeader for delegate
  3. Call the delegate method in deleteBuildButton function

Now the code for BuildsHeader should look like this:

import UIKit

//**** The Protocol ****
protocol BuildsHeaderDelegate {
    func updateCollectionView()
}

class BuildsHeader: UICollectionReusableView {

    //**** Variable for the delegate ****
    var delegate: BuildsHeaderDelegate?

    @IBOutlet weak var headerLabel: UILabel!

    @IBOutlet weak var deleteBuildButton: UIButton!

    @IBAction func deleteBuildButton(sender: UIButton) {

        for hero in heroes{ if hero.hero.name == heroForDetails.hero.name {
            hero.builds.removeAtIndex(sender.tag)
            heroForDetails = hero
            }}

        saveToDefaults(userProfile)
        //****  Call the delegate method ****
        self.delegate?.updateCollectionView()
    }
}
  1. In the viewForSupplementaryElementOfKind method of your collection view configure the delegate property of the header, like this:

    let header = collectionView.dequeueReusableSupplementaryViewOfKind(UICollectionElementKindSectionHeader, withReuseIdentifier: "BuildsHeader", forIndexPath: indexPath) as! BuildsHeader
    //**** Set this view controller to be the header's delegate ****
    header.delegate = self
    // rest of header setup
    
  2. Make the Builds ViewController confirm to BuildsHeaderDelegate :

    class Builds: UIViewController, BuildsHeaderDelegate, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
    
  3. Implement BuildsHeaderDelegate's delegate method in the Builds view controller. you can put this right after your viewForSupplementaryElementOfKind method:

    func updateCollectionView () {
        //**** reload the collectionView ****
        self.collectionBuild.reloadData()
    }