Search code examples
swiftuibuttontouchgrid-layout

How do I create a button in swift, which senses where on the button you touch it, and gives you two values (x-axis, y-axis)


I would like to create a button in swift which has two axis (x, y), and can sense where the user is touching it, and based on that set a value between 0 and 1 for both x and y.

How do I do this?


Solution

  • EDIT: Updated answer in response to @Duncan C pointing out my mistake.

    You will need to create a custom "button", rather than use a UIButton, to achieve this. There are two possible ways to do this:

    1. create a UIView to act as the button, add gesture recognisers to detect the touches, and all the necessary methods to make it function as you wish. Then you can use the gesture recognisers location(ofTouch touchIndex: Int, in view: UIView?) -> CGPoint to determine where the touch was. Depending on the complexity of your requirements this could be a lot of work.

    2. Subclass UICOntrol to create your button. UIControl has all the touch and event handling support built in so you don't need to reinvent the wheel. All you need to do is override the method beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool

    I'd always take the second option as you get so much for free.

    Here's a quick and dirty example of such a button that should get you started:

    class BigButton: UIControl {
       override init(frame: CGRect){
          super.init(frame: frame)
          let label = UILabel()
          label.text = "Big Button"
          addSubview(label)
          label.translatesAutoresizingMaskIntoConstraints = false
          NSLayoutConstraint.activate([
             label.centerXAnchor.constraint(equalTo: self.centerXAnchor),
             label.centerYAnchor.constraint(equalTo: self.centerYAnchor)
          ])
          backgroundColor = .systemMint
          addTarget(self, action: #selector(tapped), for: .touchUpInside)
       }
       
       @objc func tapped() {
          print("button tapped")
       }
       
       required init?(coder: NSCoder) {
          fatalError("init(coder:) has not been implemented")
       }
       
       //here's the key method you need to get the touch location:
        
       override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
          super.beginTracking(touch, with: event)
          //iirc overridden method can be omitted, as I think all the default implementation does is return true.  
          // It seems to work either way.  Check the docs if you are bothered.
          let touches = event?.touches(for: self)
          if let location = touches?.first?.location(in: self) {
                print(location)
          }
          return true
       }
    }
    

    For this trivial example each time you click within the counds of the button you will fire both the bound action and the location recognition. Clicking it several times in different areas would give a console output of

    (92.5, 112.5)
    button tapped
    (14.0, 12.0)
    button tapped
    (557.0, 14.5)
    button tapped
    (513.0, 647.0)
    button tapped
    (436.0, 563.0)
    button tapped
    (436.0, 563.0)
    button tapped
    (40.5, 25.0)
    button tapped
    

    If you want these in the range 0 ... 1 you will need to work them out as a fraction of the view's size.