Reputation: 4067
So I have a button that looks like a pencil in a collection view cell xib.
Then I have this code.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "groupsCollectionViewCellIdentifier", for: indexPath) as! GroupCollectionViewCell
//add action to the edit button
cell.editButton.tag = indexPath.row
cell.editButton.addTarget(self, action: #selector(GroupsCollectionViewController.editGroupAction), for: .touchUpInside)
return cell
}
//segue to edit group
func editGroupAction() {
performSegue(withIdentifier: "editGroupSegue", sender: self)
}
But whenever I click the edit button. Nothing is happening. I wonder what's missing.
Upvotes: 11
Views: 14150
Reputation: 8333
If you declare your buttons programmatically you need to make sure they are set as lazy var
objects to ensure self
is fully defined first, before the button is initialised.
class myCell: UICollectionViewCell {
static let identifier = "myCellId"
lazy var myButton: UIButton = {
let btn = UIButton()
btn.addTarget(self, action: #selector(handleButtonPress(_:)), for .touchUpInside)
return btn
}()
// rest of cell
}
Upvotes: 1
Reputation: 3706
To enable touch action on the UIButton of your Custom UICollectionCell, add the below method in your Custom UICollectionCell class.
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
var view = myButton.hitTest(myButton.convert(point, from: self), with: event)
if view == nil {
view = super.hitTest(point, with: event)
}
return view
}
If you have to cover a number of buttons, here's a way to write the same code:
class SomeCell: UICollectionViewCell {
@IBOutlet var drawButton: UIButton?
@IBOutlet var buyButton: UIButton?
@IBOutlet var likeButton: UIButton?
@IBOutlet var sayButton: UIButton?
// fix crazy Apple bug in collection view cells:
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if let drawButton = drawButton, let v = drawButton.hitTest(
drawButton.convert(point, from: self), with: event) {
return v
}
if let buyButton = buyButton, let v = buyButton.hitTest(
buyButton.convert(point, from: self), with: event) {
return v
}
if let likeButton = likeButton, let v = likeButton.hitTest(
likeButton.convert(point, from: self), with: event) {
return v
}
// etc ...
return super.hitTest(point, with: event)
}
}
Upvotes: 3
Reputation: 310
By the way, I had to implement the hit test solution then I had this issue below:
I had a similar problem where my one of my buttons was working while another one towards the bottom of the cell was not working and it was inside a stack view. So after many hours of looking around, I found that one of the parent views (between the button and the CollectionView itself) was having a size less than the size of its subviews which in this case the touches were not reported at all since they were happening outside of the parent view... Fixed that and it's working like a charm...
To be more specific I used a compositional layout for my collection view and the view called "_UICollectionViewOrthogonalScrollerEmbeddedScrollView" which is a wrapper for the collectionView cells and the estimated size was smaller than the cell it self
Upvotes: 0
Reputation: 4481
I realize this ticket is pretty old, but I had the same problem, and found a different solution.
I ran Xcode with the view-debugger. While the button was sized correctly, it's superview had a width of 0 points. This would make it impossible to tap on this button, or any other subviews.
Just mentioning this, as it might be worth trying out.
Upvotes: 2
Reputation: 2639
I was having the same issue and after using the View hierarchy, I have noticed that an additional UIView
was added to the cell.
This view was above my UIButton
, so touch event was not passed down to the button, the didSelectItem
was triggered instead.
I solved this by using the following call:
cell.bringSubviewToFront(cell.button)
Now the event will be called.
Upvotes: 3
Reputation: 415
Try this one on your cell
cell.contentView.isUserInteractionEnabled = false
Upvotes: 8
Reputation: 251
I spent hours scouring the web for a solution to my UIButton's inside UICollectionView's not working. Driving me nuts until I finally found a solution that works for me. And I believe it's also the proper way to go: hacking the hit tests. It's a solution that can go a lot deeper (pun intended) than fixing the UICollectionView Button issues as well, as it can help you get the click event to any button buried under other views that are blocking your events from getting through:
UIButton in cell in collection view not receiving touch up inside event
Since that SO answer was in Objective C, I followed the clues from there to find a swift solution:
http://khanlou.com/2018/09/hacking-hit-tests/
--
When I would disable user interaction on the cell, or any other variety of answers I tried, nothing worked.
The beauty of the solution I posted above is that you can leave your addTarget's and selector functions how you are used to doing them since they were most likey never the problem. You need only override one function to help the touch event make it to its destination.
Why the solution works:
For the first few hours I figured the gesture wasn't being registered properly with my addTarget calls. It turns out the targets were registering fine. The touch events were simply never reaching my buttons.
The reality seems to be from any number of SO posts and articles I read, that UICollectionView Cells were meant to house one action, not multiple for a variety of reasons. So you were only supposed to be using the built in selection actions. With that in mind, I believe the proper way around this limitation is not to hack UICollectionView to disable certain aspects of scrolling or user interaction. UICollectionView is only doing its job. The proper way is to hack the hit tests to intercept the tap before it gets to UICollectionView and figure out which items they were tapping on. Then you simply send a touch event to the button they were tapping on, and let your normal stuff do the work.
My final solution (from the khanlou.com article) is to put my addTarget declaration and my selector function wherever I like (in the cell class or the cellForItemAt override), and in the cell class overriding the hitTest function.
In my cell class I have:
@objc func didTapMyButton(sender:UIButton!) {
print("Tapped it!")
}
and
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
guard isUserInteractionEnabled else { return nil }
guard !isHidden else { return nil }
guard alpha >= 0.01 else { return nil }
guard self.point(inside: point, with: event) else { return nil }
// add one of these blocks for each button in our collection view cell we want to actually work
if self.myButton.point(inside: convert(point, to: myButton), with: event) {
return self.myButton
}
return super.hitTest(point, with: event)
}
And in my cell class init I have:
self.myButton.addTarget(self, action: #selector(didTapMyButton), for: .touchUpInside)
Upvotes: 15
Reputation: 655
If you are using iOS 10.Following is working code
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
let cell = collectionView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! YourCellClass
cell.btnJoin.tag = (indexPath as NSIndexPath).row
cell.btnJoin.addTarget(self, action: #selector(YourViewControllerClassName.doSomething(_:)), for: UIControlEvents.touchUpInside)
}
Action
func doSomething(_ sender: UIButton) {
print("sender index",sender.tag)
}
Upvotes: 0
Reputation: 1555
Considering your problem I have created a demo with the details you have provided above with some minor changes.
I have modified your cellForItem
method. My modified version is as below.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell : GroupCollectionViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: "groupsCollectionViewCellIdentifier", for: indexPath) as! GroupCollectionViewCell
cell.editButton.tag = indexPath.row
cell.editButton.addTarget(self, action: #selector(editGroupAction(sender:)), for: .touchUpInside)
return cell
}
And the action method is written as below:
func editGroupAction(sender: UIButton) {
print("Button \(sender.tag) Clicked")
}
From your code I have modified below Details:
1) Way of declaration of UICollectionViewCell
Object.
2) Way of Assigning #Selector
Method to the UIButton. And at the last
3) In the action method I have print the Button tag.
With the above Changes I am getting proper result. I got the selected Index value as button tag, as assigned in code in consol. The output I am getting is as below.
Button 2 Clicked
Button 3 Clicked
Button 4 Clicked
I hope this will work for your requirement.
Upvotes: 4
Reputation: 663
Are you assign the class to the cell and link the button with controller and also assign the identifier to the segue and also use break point to check if the func is call or not
and use segue like this
func editGroupAction() {
performSegue(withIdentifier: "editGroupSegue", sender: self)
let src = self.sourceViewController as UIViewController
let dst = self.destinationViewController as UIViewController
src.navigationController.pushViewController(dst, animated:false)
}
Upvotes: 0