简体   繁体   中英

How to get index on button click of collectionview cell which is inside tableview cell

I have a collectionView embedded in tableView cell. CollectionView has multiple items, which contains button. Collection View datasource and delegates are getting set in UITableViewCell. I have to perform some action based on that button selection for that, I need to know collectionView cell indexPath and tableView cell indexPath. But not able to figure out, how to achieve this. Tried using delegates, but don't know how to get collectionView reference in delegate method.

CollectionView Cell

protocol SelectedItemCellDelegate:class {
func deleteButtonDidTapped(_ cell: SelectedItemCell)
}
 class SelectedItemCell: UICollectionViewCell {
class var identifier: String{
    return String(describing: self)
}
class var nib: UINib{
    return UINib(nibName: identifier, bundle: nil)
}
@IBOutlet weak var deleteButton: UIButton!
weak var delegate: SelectedItemCellDelegate?
override func awakeFromNib() {
    super.awakeFromNib()
}

@IBAction func deleteAction(_ sender: Any) {
    delegate?.deleteButtonDidTapped(self)
}
}

ViewController

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier:SelectedItemCell.identifier, for: indexPath) as! SelectedItemCell
cell.delegate = self                        
return cell
  }

 extension PrescriptionVC: SelectedItemCellDelegate
{   
    func deleteButtonDidTapped(_ cell: SelectedItemCell) 
    {
      // Need tableview indexPath as well SelectedItemCell indexPath.
    }
}

You need two delegates

protocol selectCollectionCellDelegate {

 func selectCell(cell : UICollectionViewCell )     

}

protocol selectTableCellDelegate {

 func selectTableCell(cell : UITableViewCell , indexPath : IndexPath )

}



class YourCollectionViewCell : UICollectionViewCell {

  var tvcDelegate : selectCollectionCellDelegate


@IBAction func deleteAction(_ sender: Any) {
    tvcDelegate.selectCell(cell : self)
 }

}



class YourTableViewCell : UITableViewCell , selectCollectionCellDelegate {

  var vcDelegate : selectTableCellDelegate

 func selectCell(cell : UICollectionViewCell ){

     let indexPath : IndexPath = collectionView.indexPath(for: cell)!

     delegate.selectTableCell(cell : self , indexPath : indexPath  )
  } 

}






class YourviewController : UIViewController , selectTableCellDelegate{


 func selectTableCell(cell : UITableViewCell , indexPath : IndexPath){
   //indexPatn is IndexPath of collectionViewCell
   let tableCellindexPath : IndexPath = tableView.indexPath(for: self)!
  } 

}

In an IBAction method you can get the triggering button in the sender parameter. Rewrite your delegate method to pass in the button and the selected collection view cell:

protocol SelectedItemCellDelegate:class { func deleteButton(_ deleteButton: UIButton, tappedInCell cell: SelectedItemCell) }

Rewrite your deleteAction to pass sender as UIButton class (or any UIView class)

@IBAction func deleteAction(_ sender: UIButton) {
    delegate?. deleteButton(sender, tappedInCell: self)
}

Then you can add extensions to both UICollectionView and UITableView that let you figure out the cell that contains the button using the button's coordinates:

extension UICollectionView {
    func indexPathForCellContaining( view: UIView) -> IndexPath? {
        let viewCenter = self.convert(view.center, from: view.superview)
        return self.indexPathForItem(at: viewCenter)
    }
}

Or for table views:

public extension UITableView {

    /**
     This method returns the indexPath of the cell that contains the specified view
     - Parameter view: The view to find.
     - Returns: The indexPath of the cell containing the view, or nil if it can't be found
     */

    func indexPathForView(_ view: UIView) -> IndexPath? {
        let center = view.center

        //The center of the view is a better point to use, but we can only use it if the view has a superview
        guard let superview = view.superview else {
            //The view we were passed does not have a valid superview.
            //Use the view's bounds.origin and convert from the view's coordinate system
            let origin = self.convert(view.bounds.origin, from: view)
            let indexPath = self.indexPathForRow(at: origin)
            return indexPath
        }
        let viewCenter = self.convert(center, from: superview)
        let indexPath = self.indexPathForRow(at: viewCenter)
        return indexPath
    }
}

Since you already pass the collection view cell to the delegate you can use func indexPath(for cell: UICollectionViewCell) to get the indexPath for the CollectionView cell. If you can get a pointer to the table view you can use the above table view extension to get the index path of the button from the table view.

You are discouraged from using delegates and view math in Swift for this purpose . Use a simple callback closure

In the cell delete the code related to the protocol, declare the closure and call it when a button is pressed

class SelectedItemCell: UICollectionViewCell {
    class var identifier: String{
        return String(describing: self)
    }
    class var nib: UINib{
        return UINib(nibName: identifier, bundle: nil)
    }
    @IBOutlet weak var deleteButton: UIButton!

    var callback : (() -> Void)?

    @IBAction func deleteAction(_ sender: Any) {
        callback?()
    }
}

In the controller set the closure and handle the callback, the index path is captured.

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier:SelectedItemCell.identifier, for: indexPath) as! SelectedItemCell
    cell.callback = {
        print("button pressed", indexPath)
    }                        
    return cell
}

If items can be inserted, deleted or moved just capturing the indexPath in cellForItemAt doesn't work because the index path can change without calling cellForItemAt . In this case you have to pass the cell in the closure and get the actual index path

var callback : ((UICollectionViewCell) -> Void)?

@IBAction func deleteAction(_ sender: Any) {
    callback?(self)
}

and

cell.callback = { currentCell in
   print("button pressed", collectionView.indexPath(for: currentCell)!
} 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM