1

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.

shallowThought
  • 19,212
  • 9
  • 65
  • 112
LNI
  • 2,935
  • 2
  • 21
  • 25
  • I implemented your subclass, but sometimes it will not work: the button does not notice it is being clicked, but sometimes it does see it. This is my question: https://stackoverflow.com/questions/46391003/retrieve-tapped-uibutton-in-a-moving-uibezierpath – J. Doe Sep 24 '17 at 20:53
  • check this link https://stackoverflow.com/questions/28522104/how-to-make-a-uiimageview-tappable-and-cause-it-to-do-something-swift – Suja Jan 08 '20 at 05:34

3 Answers3

1

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))")
Nikita Gaidukov
  • 771
  • 4
  • 14
  • I tried that originally, with no luck. Also, as an experiment I subclassed UIImageView and tried adding gesture recognizers there, but taps are never detected on the image. This is despite setting isUserInteractionEnabled to true. – LNI Apr 27 '17 at 15:39
  • Do taps work if you remove animation completely? Just on a static UIImageViews. – Nikita Gaidukov Apr 27 '17 at 15:43
  • Yes it does - the issue is that when layers are animating, their "location" is not updated. – LNI Apr 28 '17 at 15:32
0

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)")
        }
    }
Lionking
  • 570
  • 6
  • 9
0

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.

LNI
  • 2,935
  • 2
  • 21
  • 25