Search code examples
iosios7swiftxcode-storyboard

UICollectionView Array index out of range


I am attempting to create a very basic collection view controller in Swift that allows me to list some numbers, and when I tap one of them, it takes me to a detail page that simply says (in a label) what number you tapped.

Storyboard: My storyboard has a navigation controller as the root and a collection view controller that gets loaded. This contains one cell, who has an identifier of cell. This cell has a button that acts as a segue to the detail page. I've assigned the datasource to point to itself (control clicked the line to the view controller to establish datasource) and i've uniquely identified the segue as showDetail.

When I load the app, it displays my buttons 7 times, but when I click any one of them, I get an index out of bounds on the line below:

Here is my code:

import Foundation
import UIKit


class WeekCollectionView : UICollectionViewController
{
    override func collectionView(collectionView: UICollectionView!, numberOfItemsInSection section: Int) -> Int
    {
        return 7
    }

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

    {
      let cell: ShiftCell = collectionView.dequeueReusableCellWithReuseIdentifier("cell", forIndexPath: indexPath) as ShiftCell
        return cell;
    }

    override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!)

    {    
        if (segue.identifier == "showDetail") {
           **//CRASHES ON THIS LINE BELOW FOR THE ARRAY [0]** 
            let indexPaths: NSArray = self.collectionView.indexPathsForSelectedItems()[0] as NSArray
            let destinationViewController = segue.destinationViewController as DetailViewController
            //var someNumber = images[indexPath.row]
            destinationViewController.mySpecialID = 1 //replace this with someNumber, always set 1 for now as a test
        }
    } 
}

My cell class (above) is simply:

class ShiftCell: UICollectionViewCell { 
   init(frame: CGRect) {
      super.init(frame: frame)
   }

  init(coder aDecoder: NSCoder!) {
       super.init(coder: aDecoder)
   }

}

I'm really not sure what I did wrong - perhaps something else needs to happen in my storyboard? I followed a tutorial from apple (in objective-c) that works perfectly, and everything code-wise is identical if i'm not mistaken!

I'm really stuck here, thanks so much if anyone can point me in the right direction! :)


Solution

  • You are getting this crash because there are no selected items. You have a button on top of the cell that is blocking the touch from reaching the cell to actually select it. You are going to have to come up with a different way to identify the row that each button is representing. A common pattern for this is to use the tag property of the button. This can store any arbitrary integer to help you identify the view.

    So, in collectionView:cellForItemAtIndexPath you should set the tag of your button to the current index:

    override func collectionView(collectionView: UICollectionView!, cellForItemAtIndexPath indexPath: NSIndexPath!) -> UICollectionViewCell!
    {
        let cell: ShiftCell = collectionView.dequeueReusableCellWithReuseIdentifier("cell", forIndexPath: indexPath) as ShiftCell
        cell.myButton.tag = indexPath.item
        return cell;
    }
    

    Then I believe the sender of the segue will be the button. You can get the index back from that:

    override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!)
    {    
        if (segue.identifier == "showDetail") {
            var possibleIndex : Int?
            if let button = sender as? UIButton {
                possibleIndex = button.tag
            }
    
            if let index = possibleIndex {
                // ...
            }
        }
    }
    

    Note: I am not positive that the sender will be the button as I normally do things through code without a storyboard. If sender is not the button, you can always do it the "old fashion way" and hook the button up to a method on the view controller from the Storyboard instead of using a segue. You would then create the new controller in code an display it however you want.