Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Perform Segue from UICollectionViewCell Button different from Cell Click

I have a UICollectionViewCell, with a UIButton. And I have two different actions. The first one, when the user presses the cell, it will segue to another view withdidSelectITemAt; the second one, when the users presses the UIButton inside the cell.
My problem is that on Swift Code of MyCollectionViewCell, I cant perform a segue, When I write the Code:

self.performSegue(withIdentifier: "toStoreFromMyDiscounts", sender: nil)

it says the error:

Value of Type MyCollectionViewCell has no member performSegue.

I also cannot write prepareForSegue, it doesn't auto complete.

How can I create a segue from a cell, that is different from click the cell itself?

like image 845
Mago Nicolas Palacios Avatar asked Apr 17 '17 18:04

Mago Nicolas Palacios


2 Answers

You can not call performSegue from your UICollectionViewCell subclass, because there is no interface declared on UICollectionViewCell like that.

The reason why it is working didSelectItemAtIndexPath() is because i suppose the delegate of your UICollectionView is a UIViewController subclass, what has the function called performSegueWithIdentifier:()`.

You need to notify your UIViewController when the button was clicked in your UICollectionViewCell, for what you have various possibilities, like KVO or using delegate.

Here is a little code sniplet, how to use KVO. This solution is great, as long as you do not care, in which cell was the button pressed.

import UIKit

class CollectionViewCell: UICollectionViewCell {
    @IBOutlet weak var button: UIButton!
}

class CollectionViewController: UIViewController {
    @IBOutlet weak var collectionView: UICollectionView!
}

extension CollectionViewController: UICollectionViewDataSource {

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

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) ->  UICollectionViewCell {
        let cell: CollectionViewCell = self.collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! CollectionViewCell
        // Add your `UIViewController` subclass, `CollectionViewController`, as the target of the button
        // Check out the documentation of addTarget(:) https://developer.apple.com/reference/uikit/uicontrol/1618259-addtarget
        cell.button.addTarget(self, action: #selector(buttonTappedInCollectionViewCell), for: .touchUpInside)
        return cell
    }

    func buttonTappedInCollectionViewCell(sender: UIButton) {
        self.performSegue(withIdentifier: "toStoreFromMyDiscounts", sender: nil)
    }
}

EDIT: If you care, in which cell the touch event has happend, use the delegate pattern.

import UIKit

protocol CollectionViewCellDelegate: class {
    // Declare a delegate function holding a reference to `UICollectionViewCell` instance
    func collectionViewCell(_ cell: UICollectionViewCell, buttonTapped: UIButton)
}

class CollectionViewCell: UICollectionViewCell {
    @IBOutlet weak var button: UIButton!
    // Add a delegate property to your UICollectionViewCell subclass
    weak var delegate: CollectionViewCellDelegate?

    @IBAction func buttonTapped(sender: UIButton) {
        // Add the resposibility of detecting the button touch to the cell, and call the delegate when it is tapped adding `self` as the `UICollectionViewCell`
        self.delegate?.collectionViewCell(self, buttonTapped: button)
    }
}

class CollectionViewController: UIViewController {
    @IBOutlet weak var collectionView: UICollectionView!
}

extension CollectionViewController: UICollectionViewDataSource {

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

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) ->  UICollectionViewCell {
        let cell: CollectionViewCell = self.collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! CollectionViewCell
        // Asssign the delegate to the viewController
        cell.delegate = self
        return cell
    }
}

// Make `CollectionViewController` confrom to the delegate 
extension CollectionViewController: CollectionViewCellDelegate {
    func collectionViewCell(_ cell: UICollectionViewCell, buttonTapped: UIButton) {
        // You have the cell where the touch event happend, you can get the indexPath like the below 
        let indexPath = self.collectionView.indexPath(for: cell)
        // Call `performSegue`
        self.performSegue(withIdentifier: "toStoreFromMyDiscounts", sender: nil)
    }
}
like image 121
dirtydanee Avatar answered Nov 08 '22 06:11

dirtydanee


Here's an elegant solution that only requires a few lines of code:

  1. Create a custom UICollectionViewCell subclass
  2. Using storyboards, define an IBAction for the "Touch Up Inside" event of your button
  3. Define a closure
  4. Call the closure from the IBAction

Swift 4+ code

class MyCustomCell: UICollectionViewCell {

        static let reuseIdentifier = "MyCustomCell"

        @IBAction func onAddToCartPressed(_ sender: Any) {
            addButtonTapAction?()
        }

        var addButtonTapAction : (()->())?
    }

Next, implement the logic you want to execute inside the closure in your

override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: MyCustomCell.reuseIdentifier, for: indexPath) as? MyCustomCell else {
            fatalError("Unexpected Index Path")
        }

        // Configure the cell
        // ...


        cell.addButtonTapAction = {
            // implement your logic here, e.g. call preformSegue()  
            self.performSegue(withIdentifier: "your segue", sender: self)              
        }

        return cell
    }

You can use this approach also with table view controllers.

like image 29
Karoly Nyisztor Avatar answered Nov 08 '22 07:11

Karoly Nyisztor