处理多点触控事件Handling Multi-Touch Events ,i% 3s'*  
*)jq7/aJ0  
处理多点触控事件,你自己的定制UIview子类别(或者,不频常,你自己的定制UIApplication或UIwindow子类别),要实行至少在其中的UIResponder方法事件处理。以下章节描述这些方法,讨论的方法处理常用手势,显示出典型的回应者物件处理一个复杂序列多点触控事件,并提出了一些技术事件处理。 bF3hyL<w  
_Q nK.r0o  
在这一章节: `kkp]s3  
m6L B<&  
事件的处理方法 0;P%+{QX  
处理轻拍的手势 FvRP^hyx9  
处理重拍的手势 vA?o9p.P+.  
在处理复杂的多点触控顺序 y rTNC jY  
事件处理技巧 OCBn;It  
l686BwL  
事件的处理方法 2z1l3  
在一个多点触控序列,应用程式分派了一系列的事件讯息。接受和处理这些信息,回应者物件类别必须实作(implement)至少有下列情形之一的方法(methods)公告 *75|?  
dfnbK  

Copy code
UIResponder : ]T7So :  
﹣(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event; F]7\ I=y  
﹣(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event; C1)'Le&r :  
﹣(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event; +SB%|@AA  
"E5,d  
XQ3 Dy ]m  
应用程式发送这些讯息,针对某个触摸(touches)阶段,当有新的或改变触摸: i)3fee'  
]"` 5q49h  
.一个或更多的手指触摸下在萤幕上,它发送了touchesBegan : withEvent :讯息。 rl2rE_)O>  
xH~ew<+  
.一个或更多的手指移动,它发送了touchesMoved : withEvent :讯息。 EPm;\H  
[UY)d5'#  
.一个或更多的手指从萤幕上离开,它发送了touchesEnded : withEvent :讯息。 9  B>  
 1w)x  
每一个这些方法的,是与触摸阶段(例如UITouchPhaseBegan) ,其中任何UItouch物件,你可以找到评估它阶段的属性。 6NwDCcd  
VwS{y>\~6  
每一个讯息,就是引用一个事件的处理方法,传送两个参数。第一,是一套UItouch物件所代表的新的或经改变触摸,涉及为特定阶段。第二个参数是一个UIEvent物件代表这个特别的事件。从事件物件,你可以得到所有接触物件为事件( allTouches )触摸物件的一个子集,过滤为View或Windows 。一些这些触摸物件代表触及这并没有改变,自上次事件信息的,或已改变,但在不同阶段进行。 \yrz`a2g  
4hHA2T  
回应者物件经常处理的事件,为某一特定阶段所获得一种或一种以上的UItouch物件在传送一套,然后再评估其属性或获得它们的位置。 (如果有任何触摸物件会做,可采取送NSSet物件的一个anyObject信息)的一个重要方法是locationInView :,其中,如果传送self的参数,产生的位置触摸在回应者物件坐标系统(previousLocationInView:) 。一个平行的方法告诉你,上次的位置触摸( previousLocationInView: ) 。 UITouch的属性案例(instance)告诉你有多轻拍(taps)已作出( tapCount ),当触摸己建立或最后己变化(timestamp)且是否触摸被强拍(swipe),如果这样,在哪个方向( info)。 _aZ9Fif.J  
     2 3v5  
         <3k@ ~Vi  
回应者类别并不实作所有三个这事件的方法上面所列。举例来说,如果它正在寻找一手指时,他们从萤幕离开,它只需实作touchesEnded : withEvent : 。事实上,不是执行上述的任何方法,回应者颣别可实作touchesChangedwithEvent :和监测阶段,触及在那个方法。 >0$9ok5x  
F W#AFl  
     >Uscd+W1$i  
如果一个回应者建立持久的物件,而在处理事件过程中多点触摸序列,它应实作touchesCanceled处置这些物件时,该系统取消了序列。取消经常发生时,一个外部事件,举例来说,一通电话打乱目前的应用程序的事件处理。注意到一回应者物件个也应处置这些同样的物体,当它接收最touchesEnded : withEvent :留言,为一个多点触摸序列。 (见"事件处理技术" Event-Handling Techniques),以了解如何确定最后的触摸在一个序列) 。 MU#{0xHh  
6[+:9n 44  
处理轻拍的手势 t'?&C9Y\  
_-yU En461  
一件很平常的手势,在iPhone的应用程式中是轻拍(tap):使用者他或她的手指轻拍(taps)一个物件。回应者物件能回应一个单一的轻拍中的一个方法,一个双轻拍(double-tap)在另一个,并可能分三轻拍(tripe-tap),但另一种方式表达。以确定有多少次使用者轻拍回应者物件,你会得到tapCount属性值的一个UITouch物件。 I0xIfoJ^  
X :J?  
最好的地方找到这个值(Value)所采用的方法touchesBegan : withEvent :和touchesEnded : withEvent :。在很多情况下,后者的方法是首选,因为它对的回应至于触摸阶段在使用者轻离开拍。由寻找该轻拍的计算(count)轻拍后往上(touch-up)阶段( UITouchPhaseEnded ) ,你确保手指,实在是打字中,比如,在案例,轻拍下(touching down)然后拖曳(dragging )。 Yx&URTd/`  
}!6tPcF  
     }3}"Hv$  
在列表8-1 , touchesEnded : withEvent :方法实作回应双轻拍(double-tap)手势缩放的内容显示在一个scroll View。 sy_0AV*1}  
XvG/jqao  
     3 EFs WFY  
列表8-1处理双双轻拍(double-tap)手势 ?P D!4Y@ d  
K~0%J qow  
Copy code
- (void) touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event 04_]+J[C  
XX2q"cw  
{ eK4!fqsFn  
zhY8U2  
     UIScrollView *scrollView = (UIScrollView*)[self superview]; w+&&ag  
38j4Qquk:  
     UITouch *touch = [touches anyObject]; k)MmR  
wSoT7nj|  
     CGSize size; vseD)yd;S  
JUO4sGB'  
     CGPoint point; i[I_NE_AD  
Ro=JS#<[  
     if([touch tapCount] == 2) { bip 2"=  
k\_KjX\-"  
         if(![_viewController _isZoomed]) { K~E%{W^  
|+e4/6[\q  
             point = [touch locationInView:self]; u"2n  
hzJx`6|9  
             size = [self bounds].size; ?[gyE0j #  
8tT0,bS-  
             point.x /= size.width; AGI~JW1_^  
W!`]4  
             point.y /= size.height; Q\a~ VUgP  
. KJiN  
             [_viewController _setZoomed:YES]; n C'^QxJ  
,mq ZWX:  
             size = [scrollView contentSize]; #j( ovcR  
\23N8  
             point.x *= size.width; a(rCL('mj  
tupZOY  
             point.y *= size.height; "QyG"^@  
`*(';R>.  
             size = [scrollView bounds].size; auN+K=p  
*(6vA\fR  
             point.x -= size.width / 2; y(AP"=cc0b  
$INh 0  
             point.y -= size.height / 2; xYGs<z+&B  
3#CLe2)  
             [scrollView setContentOffset:point animated:NO]; Z1|hJX) a  
I_-wg,  
         } 1}h\   
C=uz o  
         else \[>CoK  
 +-J<=iFC  
         [_viewController _setZoomed:NO]; .Js|`aA  
DW[~BPE4  
     } S@!%R-"P  
6> )dKN  
}
-ic+X(@.f  
(Q+0*5w  
一个复杂出现时,回应者物件要处理的单轻拍和双轻手势有不同的方式。举例来说,一个单轻拍可能会选择物件和一个双轻拍可能会显示View为编辑该项目这就是双轻拍。又是怎样回应者物件知道,就是单轻拍,是不是第一次的一个组成部分双轻里是如何回应者物件可以处理这种情况使用事件处理方法只是描述: eQ>c2efL4  
zFT\Y(6  
1. e_8L7r  
在touchesEnded : withEvent :当自轻拍计数是一个,该回应者物件发送itself本身是一个performSelector : withObject : afterDelay :讯息。在选择确定了另一种方法实作,由回应者处理单轻拍手势;物件为第二个参数是相关UITouch物件;延误(delay)是了一些合理时间间隔去区分单轻拍和双轻手势。 >av~58k/  
'1\+_HqvR  
2. ROWrKq_  
在touchesBegan : withEvent :,如果轻拍计数是两个,回应者物件对取消悬而未决的延迟履行发送本身cancelPreviousPerformRequestsWithtarget :消息。如果轻拍计数,是不是两个,该方法所确定的选择在1.为单轻拍手势是引用后拖延。 1~C2v]G  
|]u+A' O  
3. H, $9p0cO  
在touchesEnded : withEvent : ,如果轻拍计数是两个是两个,回应者履行必要行动,处理双轻手势。 j|ys;mb`?  
51eP ^DL)e  
处理重拍的手势 $ Kny3Uc  
w kN;)@R  
当使用者非常迅速的一个或一个以上的手指穿过萤幕时,系统认为这是重拍的手势。重拍手势常常被用来滚动的内容或去向前和向后的一个相连的一系列项目。该系统监视涉及在屏幕上动作,如果用手指的方向和速度都是一致可能是重拍手势,它合集info属性的相关UITouch物件;属性表明不仅发生一重拍,但其方向。由于这种检测需要跟踪的触摸随着时间的推移,行踪,可在一个UITouchPhaseMoved阶段,并没有被标示为一重拍,即使后来变成了有标记的,因为重拍。 B 5K&w  
0um(7}FQH  
注:一重拍手势,是编程等于一快速轻拍手势,虽然他们有不同的人机界面的内涵。还能用,快速轻拍是使用转移页面之间的内容,如照片。一重拍,是用来改变一些元素;举例说,重拍在一封电子邮件上,造成删除按钮出现。 !HgO9e|=  
I+0QZX\  
但是,该系统可能稍后不标注触摸作为重拍,如果随后跟踪的手指显示了运动不一致一重拍的手势。如果一个回应者物件在寻求一个重拍在一个特定方向,并没有理会,最后重拍触摸的状态,它可以测试info属性的任何一项UIResponder事件的处理方法。一般来说,你最有兴趣,是否触动作是一重拍,在触摸物件的UITouchPhaseEnded阶段。你会因此寻找和评估,重拍常数在touchesEnded : withEvent :方法。 I<X}Ga;G  
Xl!^G{X9  
     _:G)(-g)s  
列表8-2显示一个简单touchesEnded : withEvent :实作是为了寻找重拍(swipes)至左和右,并执行适当的行动方向。 uE!$(jlM  
es<MA^ KbB  
列表8-2处理重拍的手势 :_Qj\[o  
{^[B3t11`  
Copy code
-- (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event { r`)fjb7Mm  
K=`~z[Jh  
     UITouch *touch = [touches anyObject]; q#*5]w>,_#  
:'s(-.0  
     if (touch.info & UITouchInfoSwipedRight) { ftXcK  
fmWQ-=F  
         [self showPreviousImage]; e 7J3 grg  
/%7M@"jz  
     } else if (touch.info & UITouchInfoSwipedLeft) { CY;T;:E  
H!ODs?x,I3  
         [self showNextImage]; Adb<ra -  
3Mn~48\ d  
     } &V?z7V,  
vAftg %&X  
     return; XWr* PoU  
/%4}6:Hb&  
} .2|^ !  
SM]mmQk/z  
Ra 5)ne:  
该方法取得了一个触摸物件从通过在设置评估bit mark物件的info属性,以确定是否UITouchInfoSwipedRight和UITouchInfoSwipedLeft是常量。注意,当该系统探测到重拍的手势,在一个方向,如"向右上角的萤幕上" ,它规定了两个常数bit mark比,以显示这个方向(例如, UITouchInfoSwipedRight和UITouchInfoSwipedUp ) 。 Vc~u/`w$  
t=95WM}  
在处理复杂的多点触控顺序 >0DSGsS V  
b8Q ?H#5kV  
轻拍(taps)及重拍(swipes)是简单的手势。处理多点触控摸顺序是更为复杂-效果上,诠释一个特定应用手势取决于哪些应用程式正试图完成的任务。你可能要跟踪所有涉触模的阶段,录制触摸属性已改变,变更内部状态的。 VV4%vI  
kZ2vs#  
最好的方法传达你如何可能处理复杂的多点触控摸顺序,用一个例子。列表8-3日,显示出如何定制UIView物件回应触及动态的"欢迎"的标语牌围绕在萤幕上当用手指的动作,当用户重拍的手势,并改变欢迎的语言的。 LHdqsb}z  
`[{:\  
Copy code
(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event  d7^5H  
xpJU?>o4  
{ ;paqKs5nw3  
T(#IY  
     UITouch *touch = [[event allTouches] anyObject]; e)w'CZV{  
!q2A'.S"D  
     // Only move the placard view if the touch was in the placard view Zet'q;`  
,%g%B3h7}  
     if ([touch view] != placardView) { {aUU0%,o  
D7EF1wt/  
         // On double tap outside placard view, update placard's display string BCK3>~l{  
qkx uF:U'%  
         if ([touch tapCount] == 2) { *+7+Qpr  
r<GT#' ZF  
             [placardView setupNextDisplayString]; B{HAaObk  
ol(lwN}"  
         } 4afw'36  
N1u~?w"m  
         return; 72LA=$C,  
6t[mB  
     } H7--:cKo  
Uy+v6qJ  
     // "Pulse" the placard view by scaling up then down 3u?mf[3i  
XeAK7Ln  
     // Use UIView's built-in animation FN@'tBo2L  
-,('}S[_B`  
     [UIView beginAnimations:nil context:NULL]; nl vB\il  
5{FO> 8(  
     [UIView setAnimationDuration:0.5]; -#FXSrc  
-Wp|{Yn [  
     CGAffineTransform transform = CGAffineTransformMakeScale(1.2, 1.2); F>8|  
"c5wVc4  
     placardView.transform = transform; 9:&ZLZq=  
xo@uL35  
     [UIView commitAnimations]; m7v CSv  
DN7MjYq8N}  
     [UIView beginAnimations:nil context:NULL]; )> ;]=  
Aqe~/=%+  
     [UIView setAnimationDuration:0.5]; ^JA >#Cs  
~+\ F~:~.  
     transform = CGAffineTransformMakeScale(1.1, 1.1); pbvKF's  
Fk%oHi-;  
     placardView.transform = transform; dY*EGFY  
$axqcpU  
     [UIView commitAnimations]; N I8q}`_F  
]N=UU Fnl  
     // Move the placardView to under the touch l(>-5PNLig  
-+g B5htI  
     [UIView beginAnimations:nil context:NULL]; 8j8|Z/SD  
fV'B]>-  
     [UIView setAnimationDuration:0.25]; `/ki*T  
+$&wt%  
     placardView.center = [self convertPoint:[touch locationInView:self] fromView:placardView]; q(O/t@G$b  
!]Mow6"iJ  
     [UIView commitAnimations]; )mATkrP  
V-/ql/f  
} iTiO=X7F  
b fK-mg  
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event =y-m=:.M  
wlzu}wTcx  
{ &\<fE!T^  
oW9aWrg  
     UITouch *touch = [[event allTouches] anyObject]; %d.? DJJ)  
Q3 B/?%]  
     // If the touch was in the placardView, move the placardView to its location |n zwv  
 vU9Y  
     if ([touch view] == placardView) { :<Mxz<5  
2P[- 58  
         CGPoint location = [touch locationInView:self]; 5i>1>HH  
#G 0x>  
         location = [self convertPoint:location fromView:placardView]; N%Ta=`G  
{VHnDbGNs  
         placardView.center = location; UV-4QULz=|  
Rpn/Y#G}5  
         return; GlSfU!  
jr0,$q8.  
     } _^]*Z ^  
Zf"-PtoO@  
} $;z{ C  
"qY*Dp Z  
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event -EC G[x  
Y]H ' [K  
{ >i^xi;l O  
i7 X #^  
     UITouch *touch = [[event allTouches] anyObject]; +9(vDF>   
@4Yg=Um,  
     // If the touch was in the placardView, bounce it back to the center .TIVq-.=9)  
SkV&rZ  
     if ([touch view] == placardView) { NYyb,j  
< Gu//i:  
         // Disable user interaction so subsequent touches don't interfere with animation .@T,Hv  
c|1Kk   
         self.userInteractionEnabled = NO; Eq siDY'6)  
{ag.NRW  
         [self animatePlacardViewToCenter]; V\;Fm1@o|b  
}uAb:/V+  
         return; PoX.=  
0B${u?>B^  
     } =(|F1K})  
'sK.|ZGeE  
}
P%<%:.  
`b kU7qU_  
注:定制客户化View,重新划定自己在回响应事件的处理,一般只应制定一套状态在事件的处理方法,并履行所有的绘画在drawrect :方法。要了解更多关于提请查看内容,参见" Graphics and Drawing. " 。 @OGH%   
9ksNOY  
程式码则表8-4说明了两种不同的技术。首先,它处理所有接触阶段,在touchesChangedWithEvent :方法,取代由个人接触阶段方法。其次,它处理多点接触,利用每一个接触去演奏"prong" ,在模拟乐器( a kalimba ) a slWfnB  
VC#}QFK  
列表8-4处理多点触控在touchesChangedWithEvent: xc:?kZ1+  
g]SI|mo  
Copy code
- (void)touchesChangedWithEvent:(UIEvent *)event Ya&\'BAd  
~?W\b!@?  
{ HW H}  
9v 2|u'Hs  
     // if the instrument body is showing... \Nf&6@sL3  
{t`z W>KQ  
     if (self.frontShowing) { X"MOY[pd  
 @RgX^{  
         //iterate through all the touches currently active Pg(7W;Sx  
XgWLKU  
         NSSet *touches = [event allTouches]; 878;LYI  
"O}*5xoG  
         for (UITouch *myTouch in touches) { ky^k{P Q:2  
:k%b^CmY*  
         CGPoint location = [myTouch locationInView:self]; Z8:8]4/~  
3^oD5 l  
         //if it's a new touch 3AC Vg  
T+vo\&D  
             if (myTouch.phase == UITouchPhaseBegan) { ,oS{5:#  
:P~_r;  
                 //test for all kalimba prongs t^l&Fp  
( I dXZ  
                 NSInteger i; CP=]3n  
E~,~/V6+\Q  
                 for (i = 0; i < NUMBER_OF_PRONGS ; i++) { x:-=3w:K@  
S!qRG/p  
                     if (CGRectContainsPoint(hitRectPlays, location)) { g080J  
#|dky-bI  
                         //got one... play the note and make it glow 7`EZ!1_|  
sr]PBM  
                         [[InstrumentModel sharedInstrumentModel] playNoteForRegion:(i)]; fUxD_* r  
! ,+>y,A  
                         [[self.glowLayers objectAtIndex:i] setHidden: NO]; ADW,8fi:d  
pn{@Y~NvB  
                     } 7tP-Cvn74p  
PqvAsK't  
                 } o&h )R:"  
=su8#g,<  
             } else if (myTouch.phase == UITouchPhaseMoved) { J# !;y4.  
-cT 1$/  
             CGPoint oldLocation = [myTouch previousLocationInView:self]; ;=5_4;)2  
x+~ wC~  
             // if touch moved off a prong, stop glowing sW nCk  
DvC^fj/F+  
             NSInteger i; XQI[ofNrXY  
s\$U0=   
             for (i = 0; i < NUMBER_OF_PRONGS ; i++) { A0d%"p,s\  
JZNL|>*h  
                 if (CGRectContainsPoint(hitRectPlays, oldLocation) && BFfAi:ly  
:?<g#?;6b  
                       !CGRectContainsPoint(hitRectPlays, location)) { C/EGX  
hO`G?Rm  
                     [[self.glowLayers objectAtIndex:i] setHidden: YES]; &I]>om. |  
*(3Kp>  
                     }  g{[ !c2  
'Y{ 2?0T  
                 } z4n`l1N2  
%`K8r">  
             } else if (myTouch.phase == UITouchPhaseEnded) { l36Q s*L  
2 `iw~qM  
             // if touch ends on a prong, stop glowing k@B"=p*  
xP`G|~U_  
             NSInteger i; h`#q:PG  
h'L}1^qj  
             for (i = 0; i < NUMBER_OF_PRONGS ; i++) { }P0k c{xr  
6#Y Fz4  
                 if (CGRectContainsPoint(hitRectPlays, location)) { G^_J  
oy`tx_F &  
                     [[self.glowLayers objectAtIndex:i] setHidden: YES]; |GG"1gcQ+  
]dmb+7  
                     } >?EI-Fh?  
-VlaZ21tG  
                 } &u*e?Dn{  
(+$ZA ?F   
             } vPcP-n|g-  
N{s<ZW bQ  
         } DW.,=u?(P  
: |6i ~T  
     } F=&bOgv^  
7@QceQhShp  
}
L*'\} FaM  
!S @rH@v;  
注:程式码用在列表8-3,来自moveme的sample,你可以深入研究,以获得更深入的了解事件的处理情况。 4O&GU{@<  
[wUM$1  
事件处理技术 /)*Os2  
     vLz)31,[Fa  
这里有一些事件的处理技巧,你可以用在你的程式中。 x.MfKbT  
     N:1m-TI  
.跟踪UITouch物件的变化 Ts%pi}V  
PBee=\}&7  
在你的事件处理程式码,你可以储存有关接触位元(bit)状态,为后来的比较与变化UITouch案例(instance)。作为一个例子,说你要比较的最后位置,每一个接触到它原来的位置。在touchesBegan : withEvent :方法,你还可以得到原来的位置,每一个触摸,从locationInView属性和储存那些在CFDictionary物件使用地址的uitouch物件作为key。其后,在touchesended : withevent使用方法:你可以使用的地址,每个通过在uitouch物件,以获取该物件的原始位置,并比较其目前的位置。 (你应该使用cfdictionary物件,而不是一个nsdictionary物件;后者的复制品,其key,但uitouch颣别并不采用nscopying议定书,其中规定,为物件拷贝) 。 cTS1]!hO-  
SSJ9S  
. Hit-testing就触摸在subview或Layer }]N#K  
一个客制化View的可以用hitTest : withEvent :方法UIView或hitTest :方法CALayer找到subview或层就是接受一个触摸和妥善的处理处理事件。下面的例子侦测时, "info"的image在客户化View我一层,是轻拍。 <aj rfj'  
& FS2DN  
Copy code
- (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event { %eE#'7FI  
r7 B4@M  
     CGPoint location = [[touches anyObject] locationInView:self]; bW(0P  
-AFoT)jB  
     CALayer *hitLayer = [[self layer] hitTest:[self convertPoint:location fromView:nil]]; B #Va$[  
L).qm@sCY|  
     if (hitLayer == infoImage) { (p^Mco d  
f!4KF/h  
         [self displayInfo]; RbMrmZ86  
l3QKxX4.x  
     } /8MS |  
5!\rr "  
}
#3tjn%pP  
jr0I5  
如果你有一个客制的View与subviews ,你需要来决定是否需要处理涉及在subview等级或superview等级。如果subviews不办处理实作touchesBegan : withEvent : , touchesEnded : withEvent : , touchesMoved : withEvent :,或touchesChangedWithEvent : ,然后这些讯息传达了回应者链向superview 。但是,由于多种轻拍及多重触及正相关,与subviews那里,他们第一次发生, superview将不会接受这些触及。为确保接待,对各种触及, superview应凌驾hitTest : withEvent :要恢复本身,而不是它的任何subviews 。 BQ]C(8>k  
_3   
.决定当最后手指在一个多点摸序列已解除 ~:HAO1j  
当你想知道当最后手指在一个多点触摸顺序是否己离开View,比较有多少UITouch物件在传送一套与若干触模为了View所维系传送在UIEvent物件。例如: S `2|+#rD  
?c.Dil  
Copy code
- (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event { rRt'KD2{@  
B%YqPSm  
     if ([touches count] == [[event touchesForView:self] count]) { &(/ xI Xp  
xq\_9\t-  
         // last finger has lifted.... >/Y]K{%  
FEbQzVo<7  
     } lS&"k&.k5  
}