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)