Search code examples
iosswiftuicontrol

UIControl subclass is receiving "touchesCancelled" instead of "touchesEnded"


In my project, I have the main view, in which I add a UITapGestureRecognizer, and inside this main view, I have a subview that is a custom UIControl, which I will call UICustomButton.

The UICustomButton overrides the following methods of UIControl:

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        super.touchesBegan(touches, with: event)
        pressAnimation()
    }

    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        super.touchesEnded(touches, with: event)
        releaseAnimation()
        listener?.onClick(sender: self)
    }

    override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
        super.touchesCancelled(touches, with: event)
        releaseAnimation()
    }

The problem I am having is, all "click touches" are hitting the following callbacks:

  • touchesBegan
  • touchesCancelled

The touchesEnded callback is not being called, it's kinda being ignored and I don't know why.

How can I make touchesEnded be called instead of touchesCancelled on a touch action?


Some facts:

  • if I remove the UITapGestureRecognizer from the parent view, everything works fine;
  • even not calling the supers and overriding all touches methods, the touchesCancelled is called =/;
  • if I do a "long touch" or do a "big moving gesture", touchesEnded is called :o.

Solution

  • This is the correct behaviour for a view that has a gesture recogniser attached.

    The UIGestureRecognizer documentation says "If a gesture recognizer recognizes its gesture, the remaining touches for the view are cancelled":

    https://developer.apple.com/documentation/uikit/uigesturerecognizer

    The property cancelsTouchesInView (which defaults to true), determines whether or not touches are cancelled when a gesture is recognized:

    https://developer.apple.com/documentation/uikit/uigesturerecognizer/1624218-cancelstouchesinview

    Because a long touch and a swipe are not recognized by a tap recognizer, it doesn't interfere with them. It only intervenes when it recognizes a tap.

    If you set the recognizer's cancelsTouchesInView property to false, then the touches shouldn't be cancelled, and the touchesEnded(_:with:) method will be called as usual.

    You can set that property either in code or in Interface Builder (if you added your gesture recognizer by dragging it out in your storyboard).