佳为好友

转:手势操作-点击的不同次数引发的问题 -Differentiating Tap Counts on iOS

日期:2011.04.09

【自:也就是说,当同时注册点击1次,点击2次,点击3次的时候,会引发这样一个问题。即当识别点击三次的时候,对于点击1,2次也会响应。对于新的方法,要使用-requireGestureRecognizerToFail:,对于就的ios要使用延迟处理了。】

转:http://www.cimgf.com/2010/06/14/differentiating-tap-counts-on-ios/

In your iPhone/iPad apps you often need to know how many times your user tapped in a view. This can be challenging because, though the user may have tapped twice, you will receive the event and it will look like they tapped once as well as twice. If the user triple-tapped, you will get the event for one tap, two taps, and three taps. It can get a little frustrating, but the trick is timing. You simply have to wait a period of time to see if another tap comes. If it does, you cancel the action spawned by the first tap. If it doesn’t you allow the action to run. There’s a few little nuances to getting it to work, but it can be done. Here is how.

Overriding Touch Event Handlers

In order to know how many times your user tapped, you listen for the events in the various touch handlers. Let start with -touchesEnded. This is where we will determine how many taps we received and respond accordingly. Consider the following code.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event 
{
if(touches.count == 1)
{
if([[touches anyObject] tapCount] == 2)
{
[self handleDoubleTap];
}
else if([[touches anyObject] tapCount] == 3)
{
[self handleTripleTap];
}
else
{
[self handleSingleTap];
}
}
}

If you place a breakpoint inside each of the code blocks for the three tap counts, you will find that in the case where you triple tap, it will break in all three. If you double tap, it will break in both the double tap and single tap and of course if you single tap, it will break in the single tap branch.

In most cases, this is not desirable. You will likely want a clean differentiation between each touch as each tap count will likely mean something different. So how can we fix this? The trick is to use -peformSelector:withObject:afterDelay and then canceling the perform action if a new action occurs. Got it?

Wait For It…

If you call the method directly as we did in the sample code above, there is no way to delay when it runs nor is there a way to cancel it should another tap be received. Both of these things are necessary. If you think about it, waiting to run our -handleSingleTap method until a certain amount of time passes helps make sure it actually was a double tap. If the user taps once and nothing else occurs, we’re safe to run the -handleSingleTap code. If another tap is received in the mean time, however, we can cancel the action from the first tap. The way we do this is by changing our -touchesEnded: code to something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
- (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event 
{
if(touches.count == 1)
{
if([[touches anyObject] tapCount] == 2)
{
[self performSelector:@selector(handleDoubleTap)
withObject:nil
afterDelay:0.35];
}
else if([[touches anyObject] tapCount] == 3)
{
[self handleTripleTap];
}
else
{
[self performSelector:@selector(handleSingleTap)
withObject:nil
afterDelay:0.35];
}
}
}

Notice that we are delaying 350 milliseconds to see if another event occurs. You should also notice that we still call -handleTripleTap directly without using -performSelector:withObject:afterDelay. This is because it has now been isolated as a distinct event. If we get a triple tap, we’re pretty well assured now that a double or single tap event was not actually run as those events will have been cancelled. So how does that work? How do we cancel them?

Canceling The Action

Now that we are waiting around to see if another tap event occurs, we need to override the -touchesBegan: method. It will look something like this:

1
2
3
4
5
6
7
8
9
10
- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event
{
[NSObject cancelPreviousPerformRequestsWithTarget:self
selector:@selector(handleSingleTap)
object:nil];
 
[NSObject cancelPreviousPerformRequestsWithTarget:self
selector:@selector(handleDoubleTap)
object:nil];
}

The method -cancelPreviousPerformRequestsWithTarget:selector:object: is able to determine which action you want to cancel and then cancel it preventing the selectors, -handleSingleTap or -handleDoubleTap from being called–assuming the second or third taps occurred within the allocated 350 milliseconds. That’s all there is to it.

There *Is* a Catch

That is all there is to it, however, you have to be careful if you intend to pass parameters to your selectors. If you use a selector that takes a parameter so that you have to call something with -performSelector:withObject:afterDelay: passing an object to the second parameter, you will need that object or one identical to it in your call to cancel the action with -cancelPreviousPerformRequestsWithTarget:selector:object:. It must be able to evaluate to true when equals is called on the object passed into the object parameter. This can be tricky, but you can overcome it in one of two ways using an ivar:

  • Create an ivar to hold onto the variable that you will use as a parameter to the cancel when you first pass it to the -performSelector:withObject:afterDelay: call.

  • Create an ivar to hold onto the variable when you first enter touches ended and call -performSelector:withObject:afterDelay passing it nil for the objectparameter. Then, grab the ivar when you need it in your handler code. The cancel call can then take nil for its object parameter.

These points are crucial if you are intending to pass parameters as the cancel will fail if you try to pass a parameter and it doesn’t match what you used in your call to -performSelector:withObjecct:afterDelay:. The only parameter that doesn’t matter is the afterDelay: param.

Conclusion

I’m finding the need to differentiate tap counts more and more often so this post is really as much a way for me to keep a journal of things I need to do frequently as it is to help others figure things out too. I hope it’s been helpful to you. Until next time.

Update

So, apparently Gesture Recognizers do address this issue. I had looked at them as a possible solution, but ran into the same differentiation problems, hence this blog post. However, Ashley Clark pointed me to the -requireGestureRecognizerToFail: method, which apparently enables you to have this cancellation functionality by creating a dependency between recognizers. The code to take advantage of it looks something like this:

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
UITapGestureRecognizer *tripleTap = 
[[UITapGestureRecognizer alloc]
initWithTarget:self action:@selector(handleTripleTap:)];
 
[tripleTap setNumberOfTapsRequired:3];
[[self view] addGestureRecognizer:tripleTap];
[tripleTap release];
 
UITapGestureRecognizer *doubleTap =
[[UITapGestureRecognizer alloc]
initWithTarget:self action:@selector(handleDoubleTap:)];
 
[doubleTap setNumberOfTapsRequired:2];
[doubleTap requireGestureRecognizerToFail:tripleTap];
[[self view] addGestureRecognizer:doubleTap];
[doubleTap release];
 
UITapGestureRecognizer *singleTap =
[[UITapGestureRecognizer alloc]
initWithTarget:self action:@selector(handleSingleTap:)];
 
[singleTap setNumberOfTapsRequired:1]; // Unnecessary since it's the default
[singleTap requireGestureRecognizerToFail:doubleTap];
[[self view] addGestureRecognizer:singleTap];
[singleTap release];

So, if your project is 3.2 and later, use gesture recognizers. The effect is about the same, but the code is quite a bit cleaner.


+++++

posted on 2012-12-30 11:41 佳为好友 阅读(324) 评论(0)  编辑 收藏 引用 所属分类: UI


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理


导航

<2012年12月>
2526272829301
2345678
9101112131415
16171819202122
23242526272829
303112345

留言簿(1)

随笔分类

搜索

最新评论

评论排行榜