rgoncalv
rgoncalv

Reputation: 6035

Is it possible to cancel a touch from inside touchesBegan or touchesMoved?

In my main view in a UIViewController I have a mapView and a another view (Let's say view A) that is above mapView. Both of them have frames equal to self.view.bounds. The view A is a rectangle that is resizable similar to those used to crop images. My goal here is to let the user specify an area on the map. So, I want the user to be able to zoom in an out of the map as well as change the rectangle width and height proportions since only letting the view A to be an unrealizable square would limit it too much.

I got this project from GitHub https://github.com/justwudi/WDImagePicker from which I am using the resizable rectangle functionality. In the second picture of the Github link, there's a rectangle with 8 dots and a shaded area outside. I want to be able to let the touch pass to the map which is behind the view A if the user touches on the shaded area. Only if the user clicks on the area inside the dots or on the dots (so that he wants to resize the rectangle) I want the view A to recognize the touch. So, I modified the code on touch on the view A and have this:

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    if let touch = touches.first {

        if cropBorderView.frame.contains(touch.location(in: self)){
            print("touch contains  - touchesbegan")
            //self.isUserInteractionEnabled = true
        }
        else{
            print("Touch does not contain - touchesbegan")
            self.touchesCancelled(touches, with: event)
            //return
        }
        let touchPoint = touch.location(in: cropBorderView)

        anchor = self.calculateAnchorBorder(touchPoint)
        fillMultiplyer()
        resizingEnabled = true
        startPoint = touch.location(in: self.superview)
    }
}

override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
    print("inside touches moved")
    if let touch = touches.first {
        if cropBorderView.frame.contains(touch.location(in: self)){
            print("touch contains - touchesmoved")
            //self.isUserInteractionEnabled = true
        }
        else{
            print("Touch does not contain - touchesmoved ")
            self.touchesCancelled(touches, with: event)
            //return
        }

        if resizingEnabled! {
            self.resizeWithTouchPoint(touch.location(in: self.superview))
        }
    }
}

It is indeed recognizing the touch when I click inside and outside as I wanted, but it is not stopping the touch when I click outside. This means calling self.touchesCancelled(touches, with: event) is not working. Calling return gives a crash and does not work as well. Are there any solutions to this problem?

Thank you for your time and consideration.

Upvotes: 3

Views: 4346

Answers (1)

dive
dive

Reputation: 2210

touchesCancelled(_:with:) just a notification for UITouch, it will not work this way. As far as I understand, you implemented touch handlers in your overlay UIView, if so, you can try to replace the call to self.touchesCancelled(touches, with: event) with cancelTracking(with:) function from UIControl class implementation:

else {
    print("Touch does not contain - touchesmoved ")
    self.cancelTracking(with event)
}

Update solution, based on hitTest:

I've checked possible solutions and it seems that you can use hitTest: to avoid unnecessary touch recognitions. The following example is Swift Playground, you can tap and drag touches and see what happens in the console:

import UIKit
import PlaygroundSupport

class JSGView : UIView {

    var centerView = UIView()

    override func didMoveToSuperview() {
        frame = CGRect(x: 0, y: 0, width: 320, height: 480)
        backgroundColor = UIColor.clear

        centerView.frame = CGRect(x: 110, y: 190, width: 100, height: 100)
        centerView.backgroundColor = UIColor.blue
        addSubview(centerView)
    }

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

    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        if let touch = touches.first {
            if (hitTest(touch.location(in: self), with: event) != nil) {
                print("Touch passed hit test and seems valid")
                super.touchesCancelled(touches, with: event)
                return
            }
        }

        print("Touch isn't passed hit test and will be ignored")
        super.touchesMoved(touches, with: event)
    }

    override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        if centerView.bounds.contains(centerView.convert(point, from: self)) {
            return centerView
        }
        return nil
    }
}

class JSGViewController : UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        view.frame = CGRect(x: 0, y: 0, width: 320, height: 480)
        let customView = JSGView()
        view.addSubview(customView)
    }
}

let controller = JSGViewController()
PlaygroundPage.current.liveView = controller.view

Upvotes: 1

Related Questions