Search code examples
iostouchsubclassinguicontrol

Custom UIControl, action called twice


I'm trying to create a subclass of UIControl and track touches to change control appearance.

I don't know why but if I add the action (for .TouchUpInside) from IB or code, when I touch the control the register action method get called twice.
The stack trace tells me that me that the first call comes from _sendActionsForEvents:withEvent:, the second is not clear.
Here there is how I overridden track methods:

 override func continueTrackingWithTouch(touch: UITouch, withEvent event: UIEvent?) -> Bool {
        let touchPoint = touch.locationInView(self)
        if CGRectContainsPoint(bounds, touchPoint) {
            sendActionsForControlEvents(.TouchDragInside)
        }
        else {
            sendActionsForControlEvents(.TouchDragOutside)
        }
        return true
    }


override func beginTrackingWithTouch(touch: UITouch, withEvent event: UIEvent?) -> Bool {
    sendActionsForControlEvents(.TouchDown)
    return true
}

override func endTrackingWithTouch(touch: UITouch?, withEvent event: UIEvent?) {
    guard let tou = touch else { return }
    let touchPoint = tou.locationInView(self)
    if CGRectContainsPoint(bounds, touchPoint) {
        sendActionsForControlEvents(.TouchUpInside)
    }
    else {
        sendActionsForControlEvents(.TouchUpOutside)
    }
}

override func cancelTrackingWithEvent(event: UIEvent?) {
    sendActionsForControlEvents(.TouchCancel)
}

I've found also this answer but it doesn't seems to fit my issue, because when I add the target for .TouchUpInside event I don't get any action automatically from the event dispatcher as stated in that answer.


Solution

  • I've found that I misunderstood the documentation, and probably a lot of person out there (seeing some samples on internet).
    Overriding the mentioned methods doesn't make you able to manage event dispatching, to do that it should be probably better use sendAction:to:forEvent:.
    Those considerations comes after I made a little project with a UIControl subclass and add some actions for the most popular control events:

    • touch down
    • touch up inside
    • touch up outside
    • drag outside
    • drag inside
    • value changed

    RESULTS
    Except for value changed, all the other events are already called, even tracking methods are overriden. If we want to send a value changed we must call it by ourselves, and that makes sense because as per its name is not related to touches.
    One thing that I've found interesting is when tracking outside is called, it seems to be called when the user drags the finger about 50% more outside the bounds of the control, I was expecting right after passing its bounds

    class TestControl: UIControl {
        override func beginTrackingWithTouch(touch: UITouch, withEvent event: UIEvent?) -> Bool {
            let touchPoint = touch.locationInView(self)
            print(touchPoint)
            return true
        }
        override func continueTrackingWithTouch(touch: UITouch, withEvent event: UIEvent?) -> Bool {
            let touchPoint = touch.locationInView(self)
            print(touchPoint)
    
            return true
        }
    
        override func endTrackingWithTouch(touch: UITouch?, withEvent event: UIEvent?) {
            guard let touch = touch else { return }
            let touchPoint = touch.locationInView(self)
            print(touchPoint)
    
        }
    }
    
    class ViewController: UIViewController {
    
        @IBOutlet weak var tezst: TestControl!
    
    
        @IBAction func touchDown(sender: AnyObject){
            print("Touch Down")
        }
        @IBAction func touchUpInside(sender: AnyObject){
            print("Touch up inside")
        }
    
        @IBAction func touchUpOutside(sender: AnyObject){
            print("Touch up outside")
        }
        @IBAction func touchDragOutside(sender: AnyObject){
            print("Touch drag outside")
        }
        @IBAction func touchDragInside(sender: AnyObject){
            print("Touch drag inside")
        }
        @IBAction func valueChanged(sender: AnyObject){
            print("Value changed")
        }
    }