Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift: UICollectionView selecting cell indexPath issues

I am trying to do a Collection View whereby someone selects a cell and for each selection it takes them to another View Controller that holds content of the selection. However I'm running into difficulties as once I apply this line of code to didSelectItemAtIndexPath;

self.performSegueWithIdentifer("showDetail", sender: self)

and then run it in the Simulator the cell selection is working according the indexPath but its remembering the selections each time I select new cell. So for example each cell has a photo and label and if I select the first cell in the indexPath the segue takes me first to blank view and then to my selected cell. If I select another cell, number 3 on the indexPath the blank view is now the first cell from my previous choice after which it takes to my selected third cell . Its doing that every time. If I remove the performSegueWithIdentifer code (from Xcode 6.2 (in 6.1.1 it was random)) the selection is my previous choice and never my 'selectedCell', but then at least its only selecting once instead of twice to get to a view. There is something going wrong on the indexPath. This is the code for my prepareForSegue

override func prepareForSegue(segue: UIStoryBoardSegue, sender: AnyObject?) {
      if segue.identifer == "showDetail" {
          let detailVC:DetailViewController = segue.destinationViewController as DetailViewController
             detailVC.selectedImageName = selectedImage
             detailVC.selectedLabel = selectedLabels
             }
      }

I'm stuck on what to do & what solution to apply. Do I keep performSegueWithIdentifer code & create an Equatable to implement find(array, selection) on the indexPath? Or could I write a loop, (which seems much easier), that would run through the indexPath based upon the selections and that would remove the cell that is no longer selected. However I'm not sure what condition to write in the loop because I don't know the value of the property of the 'selectedCell' because its optional.

for (index, value) in enumerate(cellItems) {
//something here to remove 'selectedItem' in the indexPath
}

If I remove performSegueWithIdentifer code from didSelectItemAtIndexPath what can I do in my prepareForSegue to get the selection on the correct indexPath?

EDIT the complete code at didSelectItemAtIndexPath

override func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
     selectedImage = cellImages[indexPath.row] as String
     selectedLabels = cellLabels[indexPath.row] as String
     self.performSegueWithIdentifer("showDetail", sender: self)
   }

I've tried changing sender in the performSegueWithIdentifer to indexPath but the problem still remains.

EDIT 2 Complete code to my CollectionViewController

class CollectionViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate {

@IBOutlet weak var collectionView: UICollectionView!

var selectedImage = String()
var selectedLabels = String()

var cellImages:[String] = ["1.jpg", "2.jpg", "3.jpg", "4.jpg", "5.jpg", "6.jpg", "7.jpg", "8.jpg", "9.jpg", "10.jpg", "11.jpg", "13.jpg", "14jpg"]

var cellLabels:[String] = ["Photo 1", "Photo 2", "Photo 3", "Photo 4", "Photo 5", "Photo 6", "Photo 7", "Photo 8", "Photo 9", "Photo 10", "Photo 11", "Photo 12", "Photo 13", "Photo 14"]

func collectionView(collectionView: UICollectionView, numberOfNumberItemsInSection: Int) -> Int {
    return cellImages.count
}

func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
    let cell: PhotoViewCell = collectionView.dequeueReuseableCellWithReuseIdentifier("Cell", forIndexPath: indexPath) as PhotoViewCell 
    cell.labelCell.text = cellLabels[indexPath.row]
    cell.ImageCell.image = UIImage(named: cellImages[indexPath.row])
    return cell
}

 override func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
     selectedImage = cellImages[indexPath.row] as String
     selectedLabels = cellLabels[indexPath.row] as String
     self.performSegueWithIdentifer("showDetail", sender: self)
   }

override func prepareForSegue(segue: UIStoryBoardSegue, sender: AnyObject?) {
      if segue.identifer == "showDetail" {
          let detailVC:DetailViewController = segue.destinationViewController as DetailViewController
          detailVC.selectedImageName = selectedImage
          detailVC.selectedLabel = selectedLabels
          }
      }
}

PhotoViewCell

class PhotoViewCell: UICollectionViewCell {

@IBOutlet var labelCell: UILabel!
@IBOutlet var ImageCell: UIImage!

}

EDIT 3 - Amended

I tried your suggestion and unfortunately the problem is still persisting on double views - it's still passing two views before it takes me to the actual selected cell. I also amended the code slightly in the didSelectItemAtIndexPath but it still didn't fix the problem.

if let cell = collectionView.cellForItemAtIndexPath(indexPath) as? PhotoViewCell {

performSegueWithIdentifier("showDetail", sender: cell) 
}

However following your other suggestion, in my StoryBoard I have added a segue from my Collection View cell to my DetailViewController, which has the identifier "showDetail". If I remove segue nothing can be selected from my cells.

Although it seems the performSegueWithIdentifer code is the trigger for the double views because when I remove it, the cell is only being selected once, the problem was that the indexPath of the cell selection was not correct, because it's first selecting on a blank view (is that to do with the showDetail segue?), which then puts my indexPath out of sync.

EDIT - Solved

This stopped the double selections (the performSegueWithIdentifier line was removed): -

override func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
if let cell = collectionView.cellForItemAtIndexPath(indexPath) {
cellLabels[indexPath.row] as String
cellImages[indexPath.row] as String
   }
}

Many Thanks for your help !!!!

like image 204
j03T Avatar asked Mar 21 '15 13:03

j03T


2 Answers

(NOTE: I updated this for Swift 4 and more modern practices.)

I stick to UIView objects as much as possible.

override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
    guard let cell = collectionView.cellForItem(at: indexPath) else { return }

    performSegue(withIdentifier: "showDetail", sender: cell)
}

Then in prepare(for:sender:)

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    switch segue.identifier {
    case "showDetail":
        guard let indexPath = (sender as? UIView)?.findCollectionViewIndexPath() else { return }
        guard let detailViewController = segue.destination as? DetailViewController else { return }

        detailViewController.selectedImageName = cellImages[indexPath.row]
        detailViewController.selectedLabel = cellLabels[indexPath.row]

    default: return
    }
}

I used an extension I created a while ago findCollectionViewIndexPath()

extension UIView {

    func findCollectionView() -> UICollectionView? {
        if let collectionView = self as? UICollectionView {
            return collectionView
        } else {
            return superview?.findCollectionView()
        }
    }

    func findCollectionViewCell() -> UICollectionViewCell? {
        if let cell = self as? UICollectionViewCell {
            return cell
        } else {
            return superview?.findCollectionViewCell()
        }
    }

    func findCollectionViewIndexPath() -> IndexPath? {
        guard let cell = findCollectionViewCell(), let collectionView = cell.findCollectionView() else { return nil }

        return collectionView.indexPath(for: cell)
    }

}

I have a suspicion that you have a segue in the storyboard already and don't need func collectionView(, didSelectItemAtIndexPath:), but either way, the prepare segue should work.

like image 141
Jeffery Thomas Avatar answered Nov 16 '22 02:11

Jeffery Thomas


Swift 3.0

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {

    let iPath = self.collectionView.indexPathsForSelectedItems
    let indexPath : NSIndexPath = iPath![0] as NSIndexPath
    let rowIndex = indexPath.row
    print("row index = \(rowIndex)")
}
like image 43
Samiul Islam Sami Avatar answered Nov 16 '22 02:11

Samiul Islam Sami