Reputation: 131
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.
}
}
Upvotes: 4
Views: 3780
Reputation: 285280
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)!
}
Upvotes: 1
Reputation: 380
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)!
}
}
Upvotes: 5
Reputation: 131511
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.
Upvotes: 1