UIGestureRecognizer是一个基类,其实体类有:
在模拟器上的操作
1.使用require(toFail: )
如下的例子,一个view既有单击事件,又有双击事件,如下:
let t2 = UITapGestureRecognizer(target:self, action:#selector(doubleTap))
t2.numberOfTapsRequired = 2
self.v.addGestureRecognizer(t2)
let t1 = UITapGestureRecognizer(target:self, action:#selector(singleTap))
t1.require(toFail:t2) // *
self.v.addGestureRecognizer(t1)
2.UITapGestureRecognizer
与UIControl
的冲突
如下的例子,在UIControl
的实例的superView上添加一个UITapGestureRecognizer
- (void)viewDidLoad {
[super viewDidLoad];
self.control = [[CustomControl alloc] initWithFrame:CGRectMake(100, 100, 100, 100)];
self.control.backgroundColor = [UIColor redColor];
[self.control addTarget:self action:@selector(controlAction:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:self.control];
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tap:)];
[self.view addGestureRecognizer:tap];
}
- (void)controlAction:(CustomControl *)control {
NSLog(@"self.control action");
}
- (void)tap:(UITapGestureRecognizer *)tapGesture {
NSLog(@"self.view tapped");
}
当点击UIControl
时,输出为:
self.view tapped
可见其响应的是UITapGestureRecognizer
事件,如果要解决冲突该怎么做呢?使用UITapGestureRecognizer
的代理
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
if ([touch.view isKindOfClass:[CustomControl class]]) {
return NO;// ignore the touch
}
return YES;
}
可参考:
那如果是UIButton呢?会发现,点击Button时,响应的是Button的UIControlEventTouchUpInside
事件,而不是tap事件,这是为什么呢?
The UIView instance method
gestureRecognizerShouldBegin(_:)
solves the problem. It is called automatically; to modify its behavior, use a custom UIView subclass and override it. Its parameter is a gesture recognizer belonging to this view or to a view further up the view hierarchy. That gesture recognizer has recognized its gesture as taking place in this view; but by returning false, the view can tell the gesture recognizer to bow out(退出) and do nothing, not sending any action messages, and permitting this view to respond to the touch as if the gesture recognizer weren’t there.
Thus, for example, a UIButton could return false for a single tap UITapGestureRecognizer; a single tap on the button would then trigger the button’s action message and not the gesture recognizer’s action message. And in fact a UIButton, by default,does return false for a single tap UITapGestureRecognizer whose view is not the UIButton itself.
意思是:UIButton重写了gestureRecognizerShouldBegin(_:)
方法,当UITapGestureRecognizer的view不是button的时候,返回false
Other built-in controls may also implementgestureRecognizerShouldBegin(_:)
in such a way as to prevent accidental interaction with a gesture recognizer; the documentation says that aUISlider
implements it in such a way that aUISwipeGestureRecognizer
won’t prevent the user from sliding the “thumb,” and there may be other cases that aren’t documented explicitly. Naturally, you can take advantage of this feature in your own UIView subclasses as well.
3.与上面的问题类似,在父view上添加一个手势,但要求在点击子view时不响应手势
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
if ([touch.view isDescendantOfView:contentView]) {
return NO;
}
return YES;
}
4.同时响应多个手势
如果某个view有pan,pinch,rotate手势,在识别出一个手势后,只能同时响应一个手势
可实现UIGestureRecognizerDelegate
代理的如下方法,返回true
extension ViewController: UIGestureRecognizerDelegate {
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
}
如下的例子:
通过 translationInView:
获取在指定view坐标系统中的平移
The x and y values report the total translation over time. They are not delta values from the last time that the translation was reported. Apply the translation value to the state of the view when the gesture is first recognized—do not concatenate the value each time the handler is called.
注意:不是增量
所以一般在获取平移后,通常会将translation
重置为0
recoginzer.setTranslation(.zero, in: view)
通过velocity(in view: UIView?)
方法可以获取到速度向量,可实现惯性动画,如下的例子:
@IBAction func handlePan(_ recoginzer: UIPanGestureRecognizer) {
guard let recoginzerView = recoginzer.view else {
return
}
let translation = recoginzer.translation(in: view)
recoginzerView.center.x += translation.x
recoginzerView.center.y += translation.y
recoginzer.setTranslation(.zero, in: view)
guard recoginzer.state == .ended else {
return
}
let velocity = recoginzer.velocity(in: view)
let vectorToFinalPoint = CGPoint(x: velocity.x / 15, y: velocity.y / 15)
let bounds = UIEdgeInsetsInsetRect(view.bounds, view.safeAreaInsets)
var finalPoint = recoginzerView.center
finalPoint.x += vectorToFinalPoint.x
finalPoint.y += vectorToFinalPoint.y
finalPoint.x = min(max(finalPoint.x, bounds.minX), bounds.maxX)
finalPoint.y = min(max(finalPoint.y, bounds.minY), bounds.maxY)
let vectorToFinalPointLength = (
vectorToFinalPoint.x * vectorToFinalPoint.x + vectorToFinalPoint.y + vectorToFinalPoint.y
).squareRoot()
UIView.animate(
withDuration: TimeInterval(vectorToFinalPointLength / 1800),
delay: 0,
options: .curveEaseInOut,
animations: {
recoginzerView.center = finalPoint
}) { _ in
}
}
pinch手势处理也一样,例如我们需要对某个view进行缩放,通常的处理如下:
@IBAction func handlePinch(_ recognizer: UIPinchGestureRecognizer) {
guard let recognizerView = recognizer.view else {
return
}
recognizerView.transform = recognizerView.transform.scaledBy(x: recognizer.scale, y: recognizer.scale)
recognizer.scale = 1
}
rotation手势也类似
@IBAction func handleRotate(_ recognizer: UIRotationGestureRecognizer) {
guard let recognizerView = recognizer.view else {
return
}
recognizerView.transform = recognizerView.transform.rotated(by: recognizer.rotation)
recognizer.rotation = 0
}