在iOS中因为手势识别器的存在,咱们能够很是容易的识别出用户的交互手势。 系统提供的手势识别器以下:html
你们对上面的手势识别器确定不陌生, 那么问题来了:ios
1,手势识别器是怎样识别出用户手势的? 2,如何使用手势识别器? 3,各手势识别器状态,及各状态间如何进展? 4,多个手势识别器做用在同一个UIView会发生什么? 5,如何经过继承现有手势识别器来自定义?git
围绕着这几个问题, 我们一块儿深刻的学习一下 GestureRecognizer。github
手势识别经过分析 Touch events 中的数据, 识别出当前当前手指的动做。 成功识别出手势后,发送 Action message。了解手势识别器如何解释触摸仍是有好处的: 你能够利用Touch event中的数据直接解释触摸; 继承现有的手势识别器知足特定的识别需求。swift
自定义一个view, 而后重写下面的 touches... 方法。 而后将这个view 添加到superView中。 为了简单,只考虑一个手指。bash
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
let loc = (touches as NSSet).anyObject()?.locationInView(self.superview)
let oldP = (touches as NSSet).anyObject()?.previousLocationInView(self.superview)
let deltaX = (loc?.x)! - (oldP?.x)!
let deltaY = (loc?.y)! - (oldP?.y)!
var c = self.center
c.x += deltaX
c.y += deltaY
self.center = c
}
复制代码
//定义两个属性,存储识别时的状态
var decided = false //是否肯定移动方向了
var horiz = false //是否水平移动
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
decided = false
}
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
if !decided { //第一次调用, 肯定移动的方向。
decided = true
let then = (touches as NSSet).anyObject()?.previousLocationInView(self.superview)
let now = (touches as NSSet).anyObject()?.locationInView(self.superview)
let deltaX = fabs((now?.x)! - (then?.x)!)
let deltaY = fabs((now?.y)! - (then?.y)!)
horiz = deltaX>=deltaY
}
let loc = (touches as NSSet).anyObject()?.locationInView(self.superview)
let oldP = (touches as NSSet).anyObject()?.previousLocationInView(self.superview)
let deltaX = (loc?.x)! - (oldP?.x)!
let deltaY = (loc?.y)! - (oldP?.y)!
var c = self.center
if horiz{
c.x += deltaX
}else{
c.y += deltaY
}
self.center = c
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
}
复制代码
区分长按和短按主要根据 touchesBegan,touchesEnded之间的时间间隔肯定。 UITouch 对象的timestamp属性可以获得点击时的时间间隔。 代码以下:app
var time:Double = 0 //记录时间开始时的时间
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
time = (touches.first?.timestamp)!
}
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
let diff = (touches.first?.timestamp)! - time //计算间隔
if diff > 0.5{
print("long")
}else{
print("short")
}
}
复制代码
注意: 上面的代码存在一个问题: 只有当touchesEnded 时才能判断出是长按仍是短按。事实是: 若是时间超过了0.5, 就能够作出判断了,不必再等了。 代码:ide
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
time = (touches.first?.timestamp)!
performSelector("longPress", withObject: nil, afterDelay: 0.5) //延迟执行
}
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
let diff = (touches.first?.timestamp)! - time
//当时间间隔<=0.5时,判断为短按。另外还要取消 performSelector...指定的延迟消息。 否则longPress()总会调用
if diff <= 0.5{
print("short")
NSObject.cancelPreviousPerformRequestsWithTarget(self, selector: "longPress", object: nil)
}
}
func longPress(){
print("longPress")
}
复制代码
上面判断长按和短按的方法也能够应用在 单击和双击。虽然 UITouch的tapCount属性能够实现这个目标,可是并不能对单击,双击作出不一样的响应。函数
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
let ct = touches.first!.tapCount
if(ct==2){ //取消点击一次的延时执行函数
NSObject.cancelPreviousPerformRequestsWithTarget(self, selector: "singTap", object: nil)
}
}
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
let ct = touches.first!.tapCount
if ct==1{// 点第一下0.3秒后 不点击第二下, 就会执行singTap()
self.performSelector("singTap", withObject: nil, afterDelay: 0.3)
}
if ct==2{
print("doubleTap")
}
}
func singTap(){
print("singTap")
}
复制代码
如今能够实现: 长按,单击,双击,拖动,在水平, 垂直方向移动。 每种模式的代码有些很差理解了。 那么将上面这些代码组和起来,使view能够同时进行 长按,单击,双击,拖动,在水平, 垂直方向移动。。。。这是至关可怕的。写出来的代码彻底不具有可读性,还有难以理解的逻辑。这就是发明手势识别器的缘由之一。性能
// 系统提供的手势识别器都是继承 UIGestureRecognizer类。
- initWithTarget:action: //初始化方法,识别到手势后发送消息action到target
- addTarget:action: //添加 target-action
- removeTarget:action: //移除 target-action
- locationInView: //触摸点在指定view上的坐标。若是是单点触摸,就是这个点;若是是多点触摸,是这几个点的中点。(重心)
- numberOfTouches //识别到的手势对象中包含 UITouch对象的个数
- locationOfTouch:inView: //指定touch在指定view上的坐标。touch经过index指定。
- requireGestureRecognizerToFail: //只有当指定的识别器识别失败,本身才去识别
state //当前的状态
delegate //代理
enabled //关闭手势识别器
view //手势识别器所从属的view
cancelsTouchesInView // 默认为YES,这种状况下当手势识别器识别到touch以后,会发送touchesCancelled给hit-testview以取消hit-test view对touch的响应,这个时候只有手势识别器响应touch。当设置成NO时,手势识别器识别到touch以后不会发送touchesCancelled给hit-test,这个时候手势识别器和hit-test view均响应touch。
delaysTouchesBegan //默认是NO,这种状况下当发生一个touch时,手势识别器先捕捉到到touch,而后发给hit-testview,二者各自作出响应。若是设置为YES,手势识别器在识别的过程当中(注意是识别过程),不会将touch发给hit-test view,即hit-testview不会有任何触摸事件。只有在识别失败以后才会将touch发给hit-testview,这种状况下hit-test view的响应会延迟约0.15ms。
delaysTouchesEnded //默认为YES。这种状况下发生一个touch时,在手势识别成功后,发送给touchesCancelled消息给hit-testview,手势识别失败时,会延迟大概0.15ms,期间没有接收到别的touch才会发送touchesEnded。若是设置为NO,则不会延迟,即会当即发送touchesEnded以结束当前触摸。
复制代码
numberOfTapsRequired //点击次数
numberOfTouchesRequired //触摸点数
复制代码
scale //缩放比例
velocity //缩放速度
复制代码
rotation //旋转角度
velocity //缩放速度
复制代码
direction //容许的方向
numberOfTouchesRequired //触摸点数
复制代码
maximumNumberOfTouches //最多触摸点数
minimumNumberOfTouches //最少触摸点数
复制代码
minimumPressDuration //手势识别的最小时长
numberOfTouchesRequired
numberOfTapsRequired
allowableMovement //补偿用户手指在长期按压没法保持平稳的事实。eg:确认长按后能够进行拖动。
复制代码
识别器能够分为两类: 离散的, 连续的。状态之间的转换上图中很明了。
一个视图上面能够添加多个手势识别器。同时当触摸一个视图时,不只本视图的识别器在进行识别操做,视图层次结构中的父视图中的识别器也在工做。因此能够认为一个视图被不少手势识别器包围。 在这些手势识别器中, 一旦一个识别器识别到了它的手势,任何和它的触摸关联的其余手势识别器强制设置为失败状态。 有时这不是咱们想要的,好比 识别单击和双击。
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = UIColor.blueColor()
let t2 = UITapGestureRecognizer(target: self, action: "doubleTap")
t2.numberOfTapsRequired = 2
addGestureRecognizer(t2)
let t1 = UITapGestureRecognizer(target: self, action: "singleTap")
t1.requireGestureRecognizerToFail(t2) //只有t2识别失败后,t1才进行识别操做
addGestureRecognizer(t1)
}
func doubleTap(){
print("doubleTap")
}
func singleTap(){
print("singleTap")
}
复制代码
继承手势识别器,就是重写touches,和相关方法。 在方法中改变手势状态信息和一些属性信息。 通常都会调用父类的touches方法,毕竟父类的touches中作了大量的计算识别工做。 下面经过一个只能识别水平移动的手势来举例:
//
// HerizonalPanGestureRecognizer.swift
// GestureRecognizerDemo
//
// Created by 贺俊孟 on 16/5/13.
// Copyright © 2016年 贺俊孟. All rights reserved.
// 只可以水平拖动
import UIKit
import UIKit.UIGestureRecognizerSubclass //这个extension可使手势能够继承。不然你无法重写必要的方法。
class HerizonalPanGestureRecognizer: UIPanGestureRecognizer {
var origLoc:CGPoint! //记录开始时的坐标
override init(target: AnyObject?, action: Selector) {
super.init(target: target, action: action)
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent) {
origLoc = touches.first?.locationInView(view?.superview)
super.touchesBegan(touches, withEvent: event)
}
//全部的识别逻辑都是在这里进行。第一次调用时状态是 Possible
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent) {
if self.state == .Possible{
let loc:CGPoint! = touches.first?.locationInView(view?.superview)
let deltaX = fabs(loc.x-origLoc.x)
let deltaY = fabs(loc.y - origLoc.y)
//开始识别时, 若是竖直移动距离>水平移动距离,直接Failed
if deltaX <= deltaY{
state = .Failed
}
}
super.touchesMoved(touches, withEvent: event)
}
//经过重写。如今只有x 产生偏移。
override func translationInView(view: UIView?) -> CGPoint {
var proposedTranslation = super.translationInView(view)
proposedTranslation.y = 0
return proposedTranslation
}
}
// 使用
import UIKit
class BlueView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = UIColor.blueColor()
//添加水平手势识别器
let herizonalPan = HerizonalPanGestureRecognizer(target: self, action: "herizonalPan:")
addGestureRecognizer(herizonalPan)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// 进行水平移动
func herizonalPan(hp:HerizonalPanGestureRecognizer){
if hp.state == .Began || hp.state == .Changed{
let delta = hp.translationInView(superview)
var c = center
c.x += delta.x
c.y += delta.y
center = c
hp.setTranslation(CGPoint.zero, inView: superview) //将移动的值清零
}
}
}
复制代码
1, gestureRecognizerShouldBegin(gestureRecognizer: UIGestureRecognizer) -> Bool //在手势识别器超越Possible状态前发送给委托。返回No,强制识别器进入Failed状态。
2,gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWithGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool //当一个手势识别器打算声明他识别的手势时,若是这个手势会强制使另外一个手势识别器失败,失败的手势识别器会发送这个消息给他的代理。 返回yes,阻止这个失败。这时两个识别器同时工做。
3, gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldReceiveTouch touch: UITouch) -> Bool //在手势识别器开始touchesBegan:withEvent:以前调用。 返回false,忽略这个手势。
4,gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldRequireFailureOfGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool //协调两个同时发生的手势识别
5,gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailByGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool //识别两个同时发生的手势识别。
复制代码
下面举个例子: 使用委托消息来组合 UILongPressGestureRecognizer和UIPanGestureRecognizer. 经过长按使一个view抖动, 只有在抖动的过程当中才能够拖动该view。
//
// BlueView.swift
// GestureRecognizerDemo
//
// Created by 贺俊孟 on 16/5/12.
// Copyright © 2016年 贺俊孟. All rights reserved.
//
import UIKit
class BlueView: UIView {
var longP:UILongPressGestureRecognizer!
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = UIColor.blueColor()
//拖动
let pan = UIPanGestureRecognizer(target: self, action: "panning:")
pan.delegate = self
addGestureRecognizer(pan)
//长按
longP = UILongPressGestureRecognizer(target: self, action: "longPress:")
longP.delegate = self
addGestureRecognizer(longP)
}
func longPress(lp:UILongPressGestureRecognizer){
if lp.state == .Began{ //开始动画
let anim = CABasicAnimation(keyPath: "transform")
anim.toValue = NSValue(CATransform3D: CATransform3DMakeScale(1.1, 1.1, 1.1))
anim.fromValue = NSValue(CATransform3D:CATransform3DIdentity)
anim.repeatCount = HUGE
anim.autoreverses = true
lp.view?.layer.addAnimation(anim, forKey: nil)
return
}
if lp.state == .Ended || lp.state == .Cancelled{ //结束动画
lp.view?.layer.removeAllAnimations()
}
}
func panning(p:UIPanGestureRecognizer){
if p.state == .Began || p.state == .Changed{
let delta = p.translationInView(superview)
var c = center
c.x += delta.x
c.y += delta.y
center = c
p.setTranslation(CGPoint.zero, inView: superview) //将移动的值清零
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension BlueView:UIGestureRecognizerDelegate{
//长按和拖动能够同时进行
func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWithGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
//只有在长按状态时,才能够进行拖拽
override func gestureRecognizerShouldBegin(gestureRecognizer: UIGestureRecognizer) -> Bool {
if gestureRecognizer == longP{
return true
}else{
if longP.state == .Possible || longP.state == .Failed{
return false
}
return true
}
}
}
复制代码
上面的代码仅仅是为了说明代理如何使用。要实现上面的效果, 只使用UILongPressGestureRecognizer就够了:
//长按
let longP = UILongPressGestureRecognizer(target: self, action: "longPress:")
addGestureRecognizer(longP)
func longPress(lp:UILongPressGestureRecognizer){
//开始动画
if lp.state == .Began{
let anim = CABasicAnimation(keyPath: "transform")
anim.toValue = NSValue(CATransform3D: CATransform3DMakeScale(1.1, 1.1, 1.1))
anim.fromValue = NSValue(CATransform3D:CATransform3DIdentity)
anim.repeatCount = HUGE
anim.autoreverses = true
lp.view?.layer.addAnimation(anim, forKey: nil)
//获取触摸点相对于center的偏移
origOffset = CGPointMake(CGRectGetMidX(bounds)-lp.locationInView(self).x, CGRectGetMidY(bounds)-lp.locationInView(self).y)
}
//进行移动
if lp.state == .Changed{
var c = lp.locationInView(superview)
c.x += origOffset.x
c.y += origOffset.y
center = c
}
//结束动画
if lp.state == .Ended || lp.state == .Cancelled{
lp.view?.layer.removeAllAnimations()
}
}
复制代码
上面的抖动动画使用了 Core Animation. 内容挺多的,这里就不说了。 提供一下学习资料: 官方文档
OK ,关于手势识别,就说这么多。 这里只是抛砖引玉,为定制更加复杂的识别说一下思路。若是哪里存在问题,欢迎提出。 若是感受这篇博文对你有帮助,记得点赞哟!