LNI
LNI

Reputation: 3181

Detecting taps on an animating UIImageView

I am using a custom path animation on UIImageView items for a Swift 3 project. The code outline is as follows:

// parentView and other parameters are configured externally
let imageView = UIImageView(image: image)
imageView.isUserInteractionEnabled = true
let gr = UITapGestureRecognizer(target: self, action: #selector(onTap(gesture:)))
parentView.addGestureRecognizer(gr)
parentView.addSubview(imageView)
// Then I set up animation, including:
let animation = CAKeyframeAnimation(keyPath: "position")
// .... eventually ....
imageView.layer.add(animation, forKey: nil)

The onTap method is declared in a standard way:

func onTap(gesture:UITapGestureRecognizer) {
    print("ImageView frame is \(self.imageView.layer.visibleRect)")
    print("Gesture occurred at \(gesture.location(in: FloatingImageHandler.parentView))")
}

The problem is that each time I call addGestureRecognizer, the previous gesture recognizer gets overwritten, so any detected tap always points to the LAST added image, and the location is not detected accurately (so if someone tapped anywhere on the parentView, it would still trigger the onTap method).

How can I detect a tap accurately on per-imageView basis? I cannot use UIView.animate or other methods due to a custom path animation requirement, and I also cannot create an overlay transparent UIView to cover the parent view as I need these "floaters" to not swallow the events.

Upvotes: 1

Views: 698

Answers (3)

LNI
LNI

Reputation: 3181

As the layers don't update their frame/position etc, I needed to add the following in the image view subclass I wrote (FloatingImageView):

override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
    let pres = self.layer.presentation()!
    let suppt = self.convert(point, to: self.superview!)
    let prespt = self.superview!.layer.convert(suppt, to: pres)
    return super.hitTest(prespt, with: event)
}

I also moved the gesture recognizer to the parent view so there was only one GR at any time, and created a unique tag for each of the subviews being added. The handler looks like the following:

func onTap(gesture:UITapGestureRecognizer) {
    let p = gesture.location(in: gesture.view)
    let v = gesture.view?.hitTest(p, with: nil)
    if let v = v as? FloatingImageView {
        print("The tapped view was \(v.tag)")
    }
}

where FloatingImageView is the UIImageView subclass.

This method was described in an iOS 10 book (as well as in WWDC), and works for iOS 9 as well. I am still evaluating UIViewPropertyAnimator based tap detection, so if you can give me an example of how to use UIViewPropertyAnimator to do the above, I will mark your answer as the correct one.

Upvotes: 0

Lionking
Lionking

Reputation: 580

I think you can check the tap location that belongs imageView or not on the onTap function. Like this:

    func ontap(gesture:UITapGestureRecognizer) {
        let point = gesture.location(in: parentView)
        if imageView.layer.frame.contains(point) {
            print("ImageView frame is \(self.imageView.layer.visibleRect)")
            print("Gesture occurred at \(point)")
        }
    }

Upvotes: 0

Nikita Gaidukov
Nikita Gaidukov

Reputation: 771

It is not very clear what are you trying to achieve, but i think you should add gesture recognizer to an imageView and not to a parentView.

So this:

parentView.addGestureRecognizer(gr)

Should be replaced by this:

imageView.addGestureRecognizer(gr)

And in your onTap function you probably should do something like this:

print("ImageView frame is \(gesture.view.layer.visibleRect)")
print("Gesture occurred at \(gesture.location(in: gesture.view))")

Upvotes: 1

Related Questions