Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

TouchUpInside boundaries outside of UIButton

I'm currently tying to familiarise myself with UIKit under Swift and work out the best way of adding UI elements programmatically. However I'm finding that a touch can end way outside the button in which it began yet still register as a TouchUpInside event. The ViewController below is from a single view application and it is straightforward to start a touch on, say, button 17, end it on button 18 and still have buttonAction() declare "Button tapped: 17".

Any idea what I'm missing here? (Edit: This is under Xcode 6 beta 3 BTW.)

// ViewController.swift

import UIKit

class ViewController: UIViewController {
    let scrollView:UIScrollView = UIScrollView()

    var GWIDTH:Float = 0.0
    var GHEIGHT:Float = 0.0


    override func viewDidLoad() {
        super.viewDidLoad()

        GWIDTH = self.view.bounds.size.width
        GHEIGHT = self.view.bounds.size.height

        scrollView.frame = CGRectMake(10, 10, GWIDTH-10, GHEIGHT-20)
        scrollView.contentSize = CGSize(width:GWIDTH-20, height: 0)
        self.view.addSubview(scrollView)

        for currentTag in 1...30{
            var currentButton = UIButton.buttonWithType(UIButtonType.System) as UIButton
            currentButton.frame = CGRectMake(100, scrollView.contentSize.height, 100, 50)
            currentButton.backgroundColor = UIColor.greenColor()
            currentButton.setTitle("Test Button \(currentTag)", forState: UIControlState.Normal)
            currentButton.tag = currentTag
            currentButton.addTarget(self, action: "buttonAction:", forControlEvents: UIControlEvents.TouchUpInside)

            scrollView.addSubview(currentButton)
            scrollView.contentSize = CGSize(width:GWIDTH-20,height:2.0+currentButton.frame.size.height+currentButton.frame.origin.y)
        }//next
    }// end viewDidLoad()


    func buttonAction(sender:UIButton!){
        println("Button tapped: \(sender.tag)")
    }


    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }


    override func prefersStatusBarHidden() -> Bool {
        return true
    }
}
like image 234
Sledge Avatar asked Jul 14 '14 21:07

Sledge


1 Answers

Okay it's taken a fair bit of digging to get to the bottom of this but a couple of helpful Obj-C links...

UIControlEventTouchDragExit triggers when 100 pixels away from UIButton

How to correctly subclass UIControl?

http://www.bytearray.org/?p=5336 (particularly line 89)

...and it seems that the behaviour is standard due to the touch interface (personally I instinctively find the default zone excessive but I'm sure Apple did its homework) and can be overridden by either sub-classing UIControl or interrogating the position of the control event.

I've opted for the latter and here's an implementation specifically in Swift:

// ViewController.swift

import UIKit

class ViewController: UIViewController {
    let buttonCount = 3

    override func viewDidLoad() {
        super.viewDidLoad()

        for currentTag:Int in 1...buttonCount{
            var currentButton = UIButton.buttonWithType(UIButtonType.System) as UIButton
            currentButton.frame = CGRectMake(50, Float(currentTag*50), 120, 50)
            currentButton.backgroundColor = UIColor.greenColor()
            currentButton.setTitle("Test Button \(currentTag)", forState: UIControlState.Normal)
            currentButton.contentEdgeInsets = UIEdgeInsets(top:3,left:6,bottom:3,right:6)
            currentButton.tag = currentTag
            currentButton.addTarget(self,
                action: "btn_TouchDown:",
                forControlEvents: UIControlEvents.TouchDown)
            currentButton.addTarget(self,
                action: "btn_TouchDragExit:",
                forControlEvents: UIControlEvents.TouchDragExit)
            currentButton.addTarget(self,
                action: "btn_TouchUpInside:event:",
                forControlEvents: UIControlEvents.TouchUpInside)
            currentButton.sizeToFit()

            self.view.addSubview(currentButton)
        }//next
    }// end viewDidLoad()


    func btn_TouchDown(sender:UIButton!){
        println("TouchDown event: \(sender.tag)\n")
    }

    func btn_TouchDragExit(sender:UIButton!){
        println("TouchDragExit event: \(sender.tag)\n")
    }

    func btn_TouchUpInside(sender:UIButton!,event:UIEvent!){
        println("TouchUpInside event: \(sender.tag)")

        var currentTouch:CGPoint = event.allTouches().anyObject().locationInView(sender)
        println( "Point: \(currentTouch.x), \(currentTouch.y)\n" )
        if currentTouch.x > sender.frame.width{return}
        if currentTouch.x < 0 {return}
        if currentTouch.y > sender.frame.height{return}
        if currentTouch.y < 0 {return}

        println("Event ended within frame!")
    }


    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }


    override func prefersStatusBarHidden() -> Bool {
        return true
    }
}
like image 191
Sledge Avatar answered Oct 14 '22 16:10

Sledge