问题
This is what the code (below) can look like:
import UIKit
class TornadoButton: UIButton {
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)
}
}
class TestViewController: UIViewController {
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let greySubview = UIView()
greySubview.backgroundColor = .red
greySubview.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(greySubview)
greySubview.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
greySubview.centerYAnchor.constraint(equalTo: self.view.centerYAnchor).isActive = true
greySubview.widthAnchor.constraint(equalTo: self.view.widthAnchor).isActive = true
greySubview.heightAnchor.constraint(equalTo: greySubview.widthAnchor).isActive = true
let button = TornadoButton()
greySubview.addSubview(button)
button.translatesAutoresizingMaskIntoConstraints = false
button.widthAnchor.constraint(equalTo: greySubview.widthAnchor, multiplier: 0.09).isActive = true
button.heightAnchor.constraint(equalTo: greySubview.heightAnchor, multiplier: 0.2).isActive = true
//below constrains are needed, else the origin of the UIBezierPath is wrong
button.centerXAnchor.constraint(equalTo: greySubview.centerXAnchor).isActive = true
button.centerYAnchor.constraint(equalTo: greySubview.centerYAnchor, constant: self.view.frame.height * 0.35).isActive = true
self.view.layoutIfNeeded()
button.addTarget(self, action: #selector(tappedOnCard(_:)), for: .touchUpInside)
let circlePath = UIBezierPath(arcCenter: greySubview.frame.origin, radius: CGFloat(greySubview.frame.width * 0.5), startAngle: 0, endAngle: CGFloat.pi * 2, clockwise: true)
let orbit = CAKeyframeAnimation(keyPath: "position")
orbit.duration = 12
orbit.path = circlePath.cgPath
orbit.isAdditive = true
orbit.repeatCount = Float.greatestFiniteMagnitude
orbit.calculationMode = kCAAnimationPaced
orbit.rotationMode = kCAAnimationRotateAuto
button.layer.add(orbit, forKey: "orbit")
button.backgroundColor = .blue
let gr = UITapGestureRecognizer(target: self, action: #selector(onTap(gesture:)))
greySubview.addGestureRecognizer(gr)
}
@objc func onTap(gesture:UITapGestureRecognizer) {
let p = gesture.location(in: gesture.view)
let v = gesture.view?.hitTest(p, with: nil)
}
@IBAction func tappedOnCard(_ sender: UIButton) {
print(sender)
}
}
This almost works, BUT:
I can retrieve the button if the button is 100% visible on the screen. If it is, for example, 50% visible (and 50% off the screen (look picture below)), I do not retrieve the buttons tap (aka not retrieve the print).
How can I retrieve the button while it is in a running animation and it can be slightly off the screen?
回答1:
Original Answer:
It is very likely that the UITapGestureRecognizer in greySubview is consuming the touch and not letting it pass through to the button. Try this:
gr.cancelsTouchesInView = NO;
before adding the UITapGestureRecognizer to greySubview.
Edited Answer:
Please ignore my earlier answer and revert the change if you did it. My original answer did not make sense because in your case, the button is the subview of the grayView
and cancelsTouchesInView
works upwards in the view hierarchy, not downwards.
After a lot of digging, I was able to figure out why this was happening. It was because of incorrect hit testing in the TornadoButton
during animation. Since you are animating the button, you will need to override the hitTest
and the pointInside
methods of the TornadoButton
so that they account for the animated position instead of the actual position of the button when hit testing.
The default implementation of the pointInside
method does not take into account the animated presentation layer of the button, and just tries to check for the touch point within the rect (which will fail because the touch was somewhere else).
The following implementations of the methods seem to do the trick:
class TornadoButton: UIButton{
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)
if (pres.hitTest(suppt)) != nil{
return self
}
return super.hitTest(prespt, with: event)
}
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
let pres = self.layer.presentation()!
let suppt = self.convert(point, to: self.superview!)
return (pres.hitTest(suppt)) != nil
}
}
回答2:
I don't quite get, why you use buttons for showing a simple rectangle when views would work as well, but anyways... You didn't show us where you add the target to your button.
button.addTarget(self, action: #selector(ViewController.buttonPressed(_:)), for: .touchUpInside) // This is needed that your button 'does something'
button.tag = 1 // This is a tag that you can use to identify which button has been pressed
with the function buttonPressed
looking something like this:
@objc func buttonPressed(_ button: UIButton) {
switch button.tag {
case 1: print("First button was pressed")
case 2: print("Second button was pressed")
default: print("I don't know the button you pressed")
}
}
来源:https://stackoverflow.com/questions/46391003/retrieve-tapped-uibutton-in-a-running-animation-which-can-be-slightly-off-the-sc