一、概述
在APP的开发过程中,可能需要利用手势操作去实现一些功能或者效果。UIKit框架提供了检测常见手势的预定义手势识别器。 在手势开发上,最好尽可能使用预定义的手势识别器,因为它们的简单性减少了我们的的代码量。当然,我们也可以自定义一些特殊的手势,具体的可以查看官方文档学习:Creating a Custom Gesture Recognizer。iOS中的事件可以分为3大类型:触摸事件、加速计事件、远程控制事件,如下图:
二、UIResponder
在iOS中不是任何对象都能处理事件,只有继承了UIResponder的对象才能接收并处理事件。我们称之为“响应者对象”,UIApplication、UIViewController、UIView都继承自UIResponder,因此它们都是响应者对象,都能够接收并处理事件。
继承了UIResponder就可以处理事件。UIResponder内部提供了以下方法来处理事件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
//触摸事件:
-
(
void
)
touchesBegan
:
(
NSSet *
)
touches
withEvent
:
(
UIEvent *
)
event
;
-
(
void
)
touchesMoved
:
(
NSSet *
)
touches
withEvent
:
(
UIEvent *
)
event
;
-
(
void
)
touchesEnded
:
(
NSSet *
)
touches
withEvent
:
(
UIEvent *
)
event
;
-
(
void
)
touchesCancelled
:
(
NSSet *
)
touches
withEvent
:
(
UIEvent *
)
event
;
//加速计事件:
-
(
void
)
motionBegan
:
(
UIEventSubtype
)
motion
withEvent
:
(
UIEvent *
)
event
;
-
(
void
)
motionEnded
:
(
UIEventSubtype
)
motion
withEvent
:
(
UIEvent *
)
event
;
-
(
void
)
motionCancelled
:
(
UIEventSubtype
)
motion
withEvent
:
(
UIEvent *
)
event
;
//远程控制事件:
-
(
void
)
remoteControlReceivedWithEvent
:
(
UIEvent *
)
event
;
|
UIView的触摸事件处理:
UIView是UIResponder的子类,可以重写下列4个方法处理不同的触摸事件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
//一根或者多根手指开始触摸view,系统会自动调用view的下面方法:
-
(
void
)
touchesBegan
:
(
NSSet *
)
touches
withEvent
:
(
UIEvent *
)
event
//一根或者多根手指在view上移动,系统会自动调用view的下面方法(随着手指的移动,会持续调用该方法):
-
(
void
)
touchesMoved
:
(
NSSet *
)
touches
withEvent
:
(
UIEvent *
)
event
//一根或者多根手指离开view,系统会自动调用view的下面方法:
-
(
void
)
touchesEnded
:
(
NSSet *
)
touches
withEvent
:
(
UIEvent *
)
event
//触摸结束前,某个系统事件(例如电话呼入)会打断触摸过程,系统会自动调用view的下面方法:
-
(
void
)
touchesCancelled
:
(
NSSet *
)
touches
withEvent
:
(
UIEvent *
)
event
|
例如,使UIView随手指移动:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
-
(
void
)
touchesMoved
:
(
NSSet *
)
touches
withEvent
:
(
UIEvent *
)
event
{
UITouch *
touch
=
[
touches
anyObject
]
;
// 当前触摸点,设置以自己为参照,坐标原点为自己(self)的左上角
CGPoint
current
=
[
touch
locationInView
:
self
]
;
// 上一个触摸点
CGPoint
previous
=
[
touch
previousLocationInView
:
self
]
;
// 修改当前view的位置(中点)
CGPoint
center
=
self
.
center
;
center
.
x
+=
current
.
x
-
previous
.
x
;
center
.
y
+=
current
.
y
-
previous
.
y
;
self
.
center
=
center
;
}
|
三:UITouch
当用户用一根手指触摸屏幕时,会创建一个与手指相关联的UITouch对象,一根手指对应一个UITouch对象。
UITouch保存着跟手指相关的信息,比如触摸的位置、时间、阶段:
(1)当手指移动时,系统会更新同一个UITouch对象,使之能够一直保存该手指在的触摸位置。
(2)当手指离开屏幕时,系统会销毁相应的UITouch对象。
UITouch的属性:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
//触摸产生时所处的窗口:
@
property
(
nonatomic
,
readonly
,
retain
)
UIWindow *
window
;
//触摸产生时所处的视图:
@
property
(
nonatomic
,
readonly
,
retain
)
UIView *
view
;
//短时间内点按屏幕的次数,可以根据tapCount判断单击、双击或更多的点击:
@
property
(
nonatomic
,
readonly
)
NSUInteger
tapCount
;
//记录了触摸事件产生或变化时的时间,单位是秒:
@
property
(
nonatomic
,
readonly
)
NSTimeInterval
timestamp
;
//当前触摸事件所处的状态:
@
property
(
nonatomic
,
readonly
)
UITouchPhase
phase
;
|
UITouchPhase是一个枚举类型,包含:
1
2
3
4
5
6
7
8
9
|
UITouchPhaseBegan(触摸开始)
UITouchPhaseMoved(接触点移动)
UITouchPhaseStationary(接触点无移动)
UITouchPhaseEnded(触摸结束)
UITouchPhaseCancelled(触摸取消)
|
UITouch的方法:
1
2
3
|
-
(
CGPoint
)
locationInView
:
(
UIView *
)
view
;
//返回值表示触摸在view上的位置,这里返回的位置是针对view的坐标系的(以view的左上角为原点(0, 0)),调用时传入的view参数为nil的话,返回的是触摸点在UIWindow的位置。
-
(
CGPoint
)
previousLocationInView
:
(
UIView *
)
view
;
//该方法记录了前一个触摸点的位置
|
四、UIEvent
每产生一个事件,就会产生一个UIEvent对象,称为事件对象,记录事件产生的时刻和类型。
常见属性:
1
2
3
4
5
6
|
//事件类型:
@
property
(
nonatomic
,
readonly
)
UIEventType
type
;
@
property
(
nonatomic
,
readonly
)
UIEventSubtype
subtype
;
//事件产生的时间:
@
property
(
nonatomic
,
readonly
)
NSTimeInterval
timestamp
;
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
typedef
NS_ENUM
(
NSInteger
,
UIEventType
)
{
UIEventTypeTouches
,
UIEventTypeMotion
,
UIEventTypeRemoteControl
,
}
;
typedef
NS_ENUM
(
NSInteger
,
UIEventSubtype
)
{
// available in iPhone OS 3.0
UIEventSubtypeNone
=
0
,
// for UIEventTypeMotion, available in iPhone OS 3.0
UIEventSubtypeMotionShake
=
1
,
// for UIEventTypeRemoteControl, available in iOS 4.0
UIEventSubtypeRemoteControlPlay
=
100
,
UIEventSubtypeRemoteControlPause
=
101
,
UIEventSubtypeRemoteControlStop
=
102
,
UIEventSubtypeRemoteControlTogglePlayPause
=
103
,
UIEventSubtypeRemoteControlNextTrack
=
104
,
UIEventSubtypeRemoteControlPreviousTrack
=
105
,
UIEventSubtypeRemoteControlBeginSeekingBackward
=
106
,
UIEventSubtypeRemoteControlEndSeekingBackward
=
107
,
UIEventSubtypeRemoteControlBeginSeekingForward
=
108
,
UIEventSubtypeRemoteControlEndSeekingForward
=
109
,
}
;
|
一次完整的触摸过程,会经历3个状态:
1
2
3
4
5
6
7
8
9
10
11
|
//触摸开始:
-
(
void
)
touchesBegan
:
(
NSSet *
)
touches
withEvent
:
(
UIEvent *
)
event
//触摸移动:
-
(
void
)
touchesMoved
:
(
NSSet *
)
touches
withEvent
:
(
UIEvent *
)
event
//触摸结束:
-
(
void
)
touchesEnded
:
(
NSSet *
)
touches
withEvent
:
(
UIEvent *
)
event
//触摸取消(可能会经历):
-
(
void
)
touchesCancelled
:
(
NSSet *
)
touches
withEvent
:
(
UIEvent *
)
event
|
4个触摸事件处理方法中,都有NSSet *touches和UIEvent *event两个参数:
(1)一次完整的触摸过程中,只会产生一个事件对象,4个触摸方法都是同一个event参数
(2)如果两根手指同时触摸一个view,那么view只会调用一次touchesBegan:withEvent:方法,touches参数中装着2个UITouch对象
(3)如果这两根手指一前一后分开触摸同一个view,那么view会分别调用2次touchesBegan:withEvent:方法,并且每次调用时的touches参数中只包含一个UITouch对象
(4)根据touches中UITouch的个数可以判断出是单点触摸还是多点触摸
五、事件的产生和传递
发生触摸事件后,系统会将该事件加入到一个由UIApplication管理的事件队列中,UIApplication会从事件队列中取出最前面的事件,并将事件分发下去以便处理。通常,先发送事件给应用程序的主窗口(keyWindow),主窗口会在视图层次结构中找到一个最合适的视图来处理触摸事件,也就是说keyWindow最先收到触摸事件。这也是整个事件处理过程的第一步,找到合适的视图控件后,就会调用视图控件的touches方法来作具体的事件处理:
touchesBegan…
touchesMoved…
touchedEnded…
这些touches方法的默认做法是将事件顺着响应者链条(后面会讲解)向上传递,将事件交给上一个响应者进行处理。
事件传递示例:
触摸事件的传递是从父控件传递到子控件:
(1)点击了绿色的view:
UIApplication -> UIWindow -> 白色 -> 绿色
(2)点击了蓝色的view:
UIApplication -> UIWindow -> 白色 -> 橙色 -> 蓝色
(3)点击了黄色的view:
UIApplication -> UIWindow -> 白色 -> 橙色 -> 蓝色 -> 黄色
注意:如果父控件不能接收触摸事件,那么子控件就不可能接收到触摸事件
UIView不接收触摸事件的三种情况:
(1)不接收用户交互
userInteractionEnabled = NO
(2)隐藏
hidden = YES
(3)透明
alpha = 0.0 ~ 0.01
UIImageView的userInteractionEnabled默认就是NO,因此UIImageView以及它的子控件默认是不能接收触摸事件的。
六、响应者链条
响应者链条示意图:
响应者链的事件传递过程:
(1)如果view的控制器存在,就传递给控制器;如果控制器不存在,则将其传递给它的父视图。
(2)在视图层次结构的最顶级视图,如果也不能处理收到的事件或消息,则其将事件或消息传递给window对象进行处理。
(3)如果window对象也不处理,则其将事件或消息传递给UIApplication对象。
(4)如果UIApplication也不能处理该事件或消息,则将其丢弃。
触摸事件完整处理过程:
(1)先将事件对象由上往下传递(由父控件传递给子控件),找到最合适的控件来处理事件。
(2)调用最合适控件的touches…方法。
(3)如果这个控件调用了[super touches…];就会将事件顺着相应链条往下传递,传递给上一个响应者。
(4)接着就会调用上一个响应者的touches…方法。
(5)事件还可以继续往上传递,直到UIApplication,如果UIApplication也不处理该事件或消息,则将其丢弃。
上一个响应者:
如果当前这个View是控制器的View,那么控制器就是上一个响应者。
如果当前这个View不是控制器的View,那么父控件就是上一个响应者。
对上面进行总结:
发生触摸事件后,keyWindow最先拿到事件,keyWindow会在视图层次结构中找到一个最合适的视图来处理触摸事件。假设这个最合适的视图为myView,myView处理这个事件的方式为将该事件传递给自己的父View,如果父View不处理则继续往上传递,直到UIApplication。如果连UIApplication也不处理则丢弃该事件。
七、UIGestureRecognizer
为了完成手势识别,必须借助于手势识别器——UIGestureRecognizer,利用UIGestureRecognizer,能轻松识别用户在某个view上面做的一些常见手势,UIGestureRecognizer是一个抽象类,定义了所有手势的基本行为,使用它的子类才能处理具体的手势:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
//敲击
UITapGestureRecognizer
//捏合,可用于缩放
UIPinchGestureRecognizer
//拖拽
UIPanGestureRecognizer
//轻扫
UISwipeGestureRecognizer
//旋转
UIRotationGestureRecognizer
//长按
UILongPressGestureRecognizer
|
手势识别器的用法:
每一个手势识别器的用法都差不多,比如UITapGestureRecognizer的使用步骤如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
// 创建手势识别器对象
UITapGestureRecognizer *
tap
=
[
[
UITapGestureRecognizer
alloc
]
init
]
;
// 设置手势识别器对象的具体属性
// 连续敲击2次
tap
.
numberOfTapsRequired
=
2
;
// 需要2根手指一起敲击
tap
.
numberOfTouchesRequired
=
2
;
// 添加手势识别器到对应的view上
[
self
.
iconView
addGestureRecognizer
:
tap
]
;
// 监听手势的触发
[
tap
addTarget
:
self
action
:
@
selector
(
tapIconView
:
)
]
;
|
手势识别的状态:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
typedef
NS_ENUM
(
NSInteger
,
UIGestureRecognizerState
)
{
// 没有触摸事件发生,所有手势识别的默认状态
UIGestureRecognizerStatePossible
,
// 一个手势已经开始但尚未改变或者完成时
UIGestureRecognizerStateBegan
,
// 手势状态改变
UIGestureRecognizerStateChanged
,
// 手势完成
UIGestureRecognizerStateEnded
,
// 手势取消,恢复至Possible状态
UIGestureRecognizerStateCancelled
,
// 手势失败,恢复至Possible状态
UIGestureRecognizerStateFailed
,
// 识别到手势识别
UIGestureRecognizerStateRecognized
=
UIGestureRecognizerStateEnded
}
;
|
例如:
添加长按手势:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
UILongPressGestureRecognizer *
longP
=
[
[
UILongPressGestureRecognizer
alloc
]
initWithTarget
:
self
action
:
@
selector
(
longP
:
)
]
;
[
self
.
imageV
addGestureRecognizer
:
longP
]
;
//当长按时调用这个方法会调用很多次,
// 当手指长按在上面不松,来回移动时,会持续调用,所以要判断它的状态.
-
(
void
)
longP
:
(
UILongPressGestureRecognizer *
)
longP
{
if
(
longP
.
state
==
UIGestureRecognizerStateBegan
)
{
NSLog
(
@
"开始长按"
)
;
}
else
if
(
longP
.
state
==
UIGestureRecognizerStateChanged
)
{
NSLog
(
@
"长按时手指移动"
)
;
}
else
if
(
longP
.
state
==
UIGestureRecognizerStateEnded
)
{
NSLog
(
@
"手指离开屏幕"
)
;
}
}
|
添加轻扫手势:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
// 添加轻扫手势1
UISwipeGestureRecognizer *
swipe
=
[
[
UISwipeGestureRecognizer
alloc
]
initWithTarget
:
self
action
:
@
selector
(
swipe
:
)
]
;
// 轻扫手势默认是向右边称轻扫,可以设置轻扫的方法.
// 一个轻扫手势只能设置一个方法的轻扫.想要让它有多个方向的手势,必须得要设置的
swipe
.
direction
=
UISwipeGestureRecognizerDirectionLeft
;
[
self
.
imageV
addGestureRecognizer
:
swipe
]
;
// 添加轻扫手势2
UISwipeGestureRecognizer *
swipe2
=
[
[
UISwipeGestureRecognizer
alloc
]
initWithTarget
:
self
action
:
@
selector
(
swipe
:
)
]
;
swipe2
.
direction
=
UISwipeGestureRecognizerDirectionUp
;
[
self
.
imageV
addGestureRecognizer
:
swipe2
]
;
// 轻扫手势的方法
-
(
void
)
swipe
:
(
UISwipeGestureRecognizer *
)
swipe
{
// 判断的轻扫的方向
if
(
swipe
.
direction
==
UISwipeGestureRecognizerDirectionLeft
)
{
NSLog
(
@
"向左轻扫"
)
;
}
else
if
(
swipe
.
direction
==
UISwipeGestureRecognizerDirectionUp
)
{
NSLog
(
@
"向上轻扫"
)
;
}
}
|
添加拖动手势:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
// 添加拖动手势
UIPanGestureRecognizer *
pan
=
[
[
UIPanGestureRecognizer
alloc
]
initWithTarget
:
self
action
:
@
selector
(
pan
:
)
]
;
[
self
.
imageV
addGestureRecognizer
:
pan
]
;
// 当手指拖动时调用
-
(
void
)
pan
:
(
UIPanGestureRecognizer *
)
pan
{
// 拖动手势也有状态
if
(
pan
.
state
==
UIGestureRecognizerStateBegan
)
{
// 开始拖动
}
else
if
(
pan
.
state
==
UIGestureRecognizerStateChanged
)
{
// 获取当前手指移动的距离,是相对于最原始的点
CGPoint
transP
=
[
pan
translationInView
:
self
.
imageV
]
;
// 清空上一次的形变
self
.
imageV
.
transform
=
CGAffineTransformMakeTranslation
(
transP
.
x
,
transP
.
y
)
;
self
.
imageV
.
transform
=
CGAffineTransformTranslate
(
self
.
imageV
.
transform
,
transP
.
x
,
transP
.
y
)
;
// 复位,让它相对于上一次.
[
pan
setTranslation
:
CGPointZero
inView
:
self
.
imageV
]
;
}
else
if
(
pan
.
state
==
UIGestureRecognizerStateEnded
)
{
// 结束拖动
}
}
|
添加捏合手势:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
// 添加捏合手势
UIPinchGestureRecognizer *
pinGes
=
[
[
UIPinchGestureRecognizer
alloc
]
initWithTarget
:
self
action
:
@
selector
(
pinGes
:
)
]
;
// 设置代理使其能够同时支持多个手势
pinGes
.
delegate
=
self
;
[
self
.
imageV
addGestureRecognizer
:
pinGes
]
;
// 捏合时调用
-
(
void
)
pinGes
:
(
UIPinchGestureRecognizer *
)
pin
{
self
.
imageV
.
transform
=
CGAffineTransformScale
(
self
.
imageV
.
transform
,
pin
.
scale
,
pin
.
scale
)
;
// 复位
[
pin
setScale
:
1
]
;
}
|
添加旋转手势:
1
2
3
4
5
6
7
8
9
10
11
12
|
// 添加旋转手势
UIRotationGestureRecognizer *
rotation
=
[
[
UIRotationGestureRecognizer
alloc
]
initWithTarget
:
self
action
:
@
selector
(
rotation
:
)
]
;
// 设置代理使其能够同时支持多个手势
rotation
.
delegate
=
self
;
[
self
.
imageV
addGestureRecognizer
:
rotation
]
;
// 当手指开始旋转时调用.
-
(
void
)
rotation
:
(
UIRotationGestureRecognizer *
)
rotation
{
self
.
imageV
.
transform
=
CGAffineTransformRotate
(
self
.
imageV
.
transform
,
rotation
.
rotation
)
;
// 复位.
[
rotation
setRotation
:
0
]
;
}
|