3

In each cell of my collection view is a circular UIView. This has been achieved by creating a custom subclass of UIView, which I have called CircleView, and setting layer.cornerRadius = self.frame.size.width/2 in the subclass' awakeFromNib()

I want to add a gesture recognizer to each CircleView. I have done this in the collection view's cellForItemAtIndexPath:

let gestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(tap(_:)))
cell.circleView.addGestureRecognizer(gestureRecognizer)

The problem is that the gesture recognizer is called whenever a tap occurs anywhere within the bounds of the original square UIView. I want to only recognize taps that occur within the circle.

I have tried to solve this issue in the following ways:

In the CircleView's awakeFromNib() I set self.clipsToBounds = true (no effect)

Also in the CircleView's awakeFromNib() I set layer.masksToBounds = true (no effect)

Thank you in advance for your ideas and suggestions.

Austin Wood
  • 368
  • 1
  • 4
  • 14
  • Have you fixed your issue with the suggested solution? – alexburtnik Oct 16 '16 at 21:01
  • Adding a new instance of `UITapGestureRecognizer` in `cellForItemAtIndexPath` is not the best idea. This will add duplicate recognizers over and over when you're scrolling this collection view because the cells are reused. – Legonaftik Dec 13 '20 at 01:31

1 Answers1

11

You can override this method in CircleView:

override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
    let center = CGPoint(x: bounds.size.width/2, y: bounds.size.height/2)
    return pow(center.x-point.x, 2) + pow(center.y - point.y, 2) <= pow(bounds.size.width/2, 2)
}

enter image description here

All touches not belonging to the circle will be ignored.

More details:

https://developer.apple.com/reference/uikit/uiview/1622469-hittest https://developer.apple.com/reference/uikit/uiview/1622533-point

The main point is that you don't need to call neither hitTest nor pointInside methods, you just override them in your custom view and system will call them whenever it needs to know if a touch should be handled by this view.

In your case you've got a UITableViewCell with a CircleView in it, right? You've added a gesture recognizer to CircleView and overriden pointInside method, so a touch will be handled by CircleView itself if a touch point is inside the circle, otherwise event will be passed further, handled by cell and therefore didSelectRowAtIndexPath will be called.

alexburtnik
  • 7,661
  • 4
  • 32
  • 70
  • Thank you for your answer. I have added this function to CircleView, but am unsure how to use it. I have tried two ways. 1) Getting the location of the tap with `let tapLocation = gestureRecognizer.location(in: self.view)`. But now I don't know how to pass it into the function to check if it's within the circle. 2) I can instead use the collection view's `didSelectItemAtIndexPath`, here I can access the function with `cell.circleView.point`, but with this option I don't understand how to get the location of the tap to pass in. – Austin Wood Oct 17 '16 at 17:27
  • By the way, the image is very helpful for understanding _how_ the code works. Thanks! I just don't know how to use it in connection with the gesture recognizer (or collection view's didSelectItemAtIndexPath.. either would work!) – Austin Wood Oct 17 '16 at 17:31
  • @AustinWood You should not pass the location of the tap manually with this approach. If you override `pointInside` method, it will get called automatically when need. See the updated answer. – alexburtnik Oct 17 '16 at 22:44
  • Ahh now I understand. Thank you. In the code you provided, I think it should be `let center = CGPoint(x: bounds.size.width / 2, y: bounds.size.height / 2)`. In your version the center coordinates aren't divided by 2. I changed it to this and now it works for me. – Austin Wood Oct 18 '16 at 14:35
  • @AustinWood Yes, it was a mistake in the first version of my answer and I fixed it 10 minutes afterwards :) Glad that it was helpful. – alexburtnik Oct 18 '16 at 14:37
  • Oh I must have copied it immediately and didn't notice the change in the code when I read your updated explanations. Thank you again! – Austin Wood Oct 18 '16 at 19:26