13 UIGestureRecognizer and UIMenuController

来源:互联网 发布:手机网络dns被劫持 编辑:程序博客网 时间:2024/06/17 13:33
In Chapter 12, you handled raw touches and determined their course by implementing methods from
UIResponder. Sometimes you want to detect a specific pattern of touches that make a gesture, like a
pinch or a swipe. Instead of writing code to detect common gestures yourself, you can use instances of

UIGestureRecognizer.

在12章中,你使用UIResponder来识别touch;在这一章,你会学习使用UIGestureRecognizer来识别手势。


A UIGestureRecognizer intercepts touches that are on their way to being handled by a view. When
it recognizes a particular gesture, it sends a message to the object of your choice. There are several
types of gesture recognizers built into the SDK. In this chapter, you will use three of them to allow
TouchTracker users to select, move, and delete lines (Figure 13.1). You will also see how to use another

interesting iOS class, UIMenuController.

UIGestureRecognizer 会拦截touch事件,并分析是否需要触发某个手势事件。在ios的sdk中已经实现了很多类型

的手势,在这章你会学到其中的三种。(类似于android,应该是在底层的处理中拦截了touch,并交给相应的

手势对象处理)


Figure 13.1 TouchTracker by the end of the chapter




UIGestureRecognizer 的子类才能被实例化,它使用target-action模式,使用它需要先和一个view绑定。

它会拦截普通的touch事件,所以有可能会收不到touchesbegin:withevent:消息(底层被拦截);


第一个使用的子类是UITapGestureRecognizer,它需要监听2次tap,之后会通知TouchTracker清空屏幕;

- (instancetype)initWithFrame:(CGRect)r
{
self = [super initWithFrame:r];
if (self) {
self.linesInProgress = [[NSMutableDictionary alloc] init];
self.finishedLines = [[NSMutableArray alloc] init];
self.backgroundColor = [UIColor grayColor];
self.multipleTouchEnabled = YES;
UITapGestureRecognizer *doubleTapRecognizer =
[[UITapGestureRecognizer alloc] initWithTarget:self
action:@selector(doubleTap:)];
doubleTapRecognizer.numberOfTapsRequired = 2;
[self addGestureRecognizer:doubleTapRecognizer];
}
return self;
}

在上述的代码中创建UITapGestureRecognizer 对象,并attach到BNRDrawView中。


When a double tap occurs on an instance of BNRDrawView, the message doubleTap: will be sent to that
instance. Implement this method in BNRDrawView.m.
- (void)doubleTap:(UIGestureRecognizer *)gr
{
NSLog(@"Recognized Double Tap");
[self.linesInProgress removeAllObjects];
[self.finishedLines removeAllObjects];
[self setNeedsDisplay];
}

在action中清空屏幕。


Notice that the argument to the action method for a gesture recognizer is the instance of
UIGestureRecognizer that sent the message. In the case of a double tap, you do not need any
information from the recognizer, but you will need information from the other recognizers you install
later in the chapter. Build and run the application, draw a few lines, and double-tap the screen to clear
them.

注意到这个action的参数是UIGestureRecognizer ,以后我们会用到它。


You may have noticed (especially on the simulator) that during a double tap the first tap draws a small
red dot. This dot appears because touchesBegan:withEvent: is sent to the BNRDrawView on the first
tap, creating a small line. Check the console and you will see the following sequence of events:
touchesBegan:withEvent:

你会注意到第一次touch的时候会出现一个小红点,那是因为手势还没建立,view仍旧收到了toucherBegan消息。


Recognized Double Tap
touchesCancelled:withEvent:
Gesture recognizers work by inspecting touch events to determine if their particular gesture occurred.
Before a gesture is recognized, all UIResponder messages will be delivered to a view as normal.
Since a tap gesture recognizer is recognized when a touch begins and ends within a small area
in a small amount of time, the UITapGestureRecognizer cannot claim the touch is a tap just yet
and touchesBegan:withEvent: is sent to the view. When the tap is finally recognized, the gesture
recognizer claims the touch involved in the tap for itself and no more UIResponder messages will be

Gesture recognizers会根据是否在短时间内触发touch begin和touch end以判断是否是一个tap手势,

在这之前所有的touch事件都可以被view收到。

一旦识别为手势,那么之后所有的touch事件就无法被收到了。


sent to the view for that particular touch. In order to communicate this touch take-over to the view,
touchesCancelled:withEvent: is sent to the view and the NSSet of touches contains that UITouch
instance.

但仍旧会收到touchesCancelled:withEvent:消息。


To prevent this red dot from appearing temporarily, you can tell a UIGestureRecognizer to delay the
sending of touchesBegan:withEvent: to its view if it is still possible for the gesture to be recognized.
In BNRDrawView.m, modify initWithFrame: to do just this.
UITapGestureRecognizer *doubleTapRecognizer =
[[UITapGestureRecognizer alloc] initWithTarget:self
action:@selector(doubleTap:)];
doubleTapRecognizer.numberOfTapsRequired = 2;
doubleTapRecognizer.delaysTouchesBegan = YES;
[self addGestureRecognizer:doubleTapRecognizer];
}
return self;
}
Build and run the application, draw some lines, and then double-tap to clear them. You will no longer
see the red dot while double tapping.

可以设置UIGestureRecognizer 的delaysTouchesBegan 为true来延缓发送toucherBegan消息。


Multiple Gesture Recognizers
Let’s add another gesture recognizer that allows the user to select a line. (Later, a user will be able to
delete the selected line.) You will install another UITapGestureRecognizer on the BNRDrawView that
only requires one tap.
In BNRDrawView.m, modify initWithFrame:.
[self addGestureRecognizer:doubleTapRecognizer];


UITapGestureRecognizer *tapRecognizer =
[[UITapGestureRecognizer alloc] initWithTarget:self
action:@selector(tap:)];
tapRecognizer.delaysTouchesBegan = YES;
[self addGestureRecognizer:tapRecognizer];
}
return self;
}
Now, implement tap: to log the tap to the console in BNRDrawView.m.
- (void)tap:(UIGestureRecognizer *)gr
{
NSLog(@"Recognized tap");
}
Build and run the application. Tapping once will log the appropriate message to the console. The only
problem, however, is that tapping twice will trigger both tap: and doubleTap:.

现在,我们添加另外一个gesture recognizer,用来监听一次点击事件。但这样有个问题,就是如果用户点击

2次屏幕,会同时触发tap:和doubleTap:;


In situations where you have multiple gesture recognizers, it is not uncommon to have a gesture
recognizer fire when you really want another gesture recognizer to handle the work. In these cases, you
set up dependencies between recognizers that say, “Just wait a moment before you fire, because this
gesture might be mine!”

一般情况下,不希望同时触发2类手势,所以需要设置它们的触发条件。


In initWithFrame:, make it so the tapRecognizer must wait for the doubleTapRecognizer to fail
before it can assume that a single tap is not just the first of a double tap.

通过设置requireGestureRecognizerToFail:doubleTapRecognizer,这样只有doubleTap手势检测失败了

才会触发单次tap;


UITapGestureRecognizer *tapRecognizer =
[[UITapGestureRecognizer alloc] initWithTarget:self
action:@selector(tap:)];

tapRecognizer.delaysTouchesBegan = YES;
[tapRecognizer requireGestureRecognizerToFail:doubleTapRecognizer];
[self addGestureRecognizer:tapRecognizer];


Build and run the application. A single tap now takes a small amount of time to fire after the tap
occurs, but double-tapping no longer triggers the tap: message.

这样,double tap就不会出发tap手势。


Now, let’s build on the BNRDrawView so that the user can select lines when they are tapped. First, add a
property to hold onto a selected line to the class extension in BNRDrawView.m.
@interface BNRDrawView ()
@property (nonatomic, strong) NSMutableDictionary *linesInProgress;
@property (nonatomic, strong) NSMutableArray *finishedLines;
@property (nonatomic, weak) BNRLine *selectedLine;
@end
(Notice that this property is weak: the finishedLines array will hold the strong reference to the
line and selectedLine will be set to nil if the line is removed from finishedLines by clearing the
screen.)

创建了一个selectedLine 属性,注意到它是一个weak属性,因为array有拥有了它的强引用。

这样从array中移除时,就可以回收内存了。


Now, in drawRect:, add some code to the bottom of the method to draw the selected line in green.
[[UIColor redColor] set];
for (NSValue *key in self.linesInProgress) {
[self strokeLine:self.linesInProgress[key]];
}
if (self.selectedLine) {
[[UIColor greenColor] set];
[self strokeLine:self.selectedLine];
}
}

将selectLine绘制为green颜色的线。


Implement lineAtPoint: in BNRDrawView.m to get a BNRLine close to the given point.

- (BNRLine *)lineAtPoint:(CGPoint)p
{
// Find a line close to p
for (BNRLine *l in self.finishedLines) {
CGPoint start = l.begin;
CGPoint end = l.end;
// Check a few points on the line
for (float t = 0.0; t <= 1.0; t += 0.05) {
float x = start.x + t * (end.x - start.x);
float y = start.y + t * (end.y - start.y);
// If the tapped point is within 20 points, let's return this line
if (hypot(x - p.x, y - p.y) < 20.0) {
return l;
}
}
}
// If nothing is close enough to the tapped point, then we did not select a line
return nil;
}
(There are better ways to implement lineAtPoint:, but this simplistic implementation is OK for your
current purpose.)

通过lineAtPoint来获取线


The point you are interested in, of course, is where the tap occurred. You can easily get this
information. Every UIGestureRecognizer has a locationInView: method. Sending this message to
the gesture recognizer will give you the coordinate where the gesture occurred in the coordinate system
of the view that is passed as the argument.

可以使用gesture recognized的locationInView来获取相对于view的坐标;


In BNRDrawView.m, send the locationInView: message to the gesture recognizer, pass the result to
lineAtPoint:, and make the returned line the selectedLine.
- (void)tap:(UIGestureRecognizer *)gr
{
NSLog(@"Recognized tap");
CGPoint point = [gr locationInView:self];
self.selectedLine = [self lineAtPoint:point];
[self setNeedsDisplay];
}
Build and run the application. Draw a few lines and then tap on one. The tapped line should appear in
green, but remember that it takes a short moment before the tap is known not to be a double tap.

这样你就可以实现多个手势共存了。


UIMenuController
Next you are going to make it so that when the user selects a line, a menu appears right where the user
tapped that offers the option to delete that line. There is a built-in class for providing this sort of menu
called UIMenuController (Figure 13.3). A menu controller has a list of UIMenuItem objects and is
presented in an existing view. Each item has a title (what shows up in the menu) and an action (the
message it sends the first responder of the window).

使用UIMenuController 来显示一个菜单;它会有多个UIMenuItem,并且每个item都有对应的title,

可以触发相应的action给window的first responder对象。


There is only one UIMenuController per application. When you wish to present this instance, you fill
it with menu items, give it a rectangle to present from, and set it to be visible.

每个应用只能有一个UIMenuController 对象,你使用menu items充填它,并用rectangle 设置它的大小,最后设置它为可见。



Do this in BNRDrawView.m’s tap: method if the user has tapped on a line. If the user tapped somewhere
that is not near a line, the currently selected line will be deselected, and the menu controller will hide.
- (void)tap:(UIGestureRecognizer *)gr
{
NSLog(@"Recognized tap");
CGPoint point = [gr locationInView:self];
self.selectedLine = [self lineAtPoint:point];
if (self.selectedLine) {
// Make ourselves the target of menu item action messages
[self becomeFirstResponder];
// Grab the menu controller
UIMenuController *menu = [UIMenuController sharedMenuController];
// Create a new "Delete" UIMenuItem
UIMenuItem *deleteItem = [[UIMenuItem alloc] initWithTitle:@"Delete"
action:@selector(deleteLine:)];
menu.menuItems = @[deleteItem];
// Tell the menu where it should come from and show it
[menu setTargetRect:CGRectMake(point.x, point.y, 2, 2) inView:self];
[menu setMenuVisible:YES animated:YES];
} else {
// Hide the menu if no line is selected
[[UIMenuController sharedMenuController] setMenuVisible:NO animated:YES];
}
[self setNeedsDisplay];
}

以上代码就是当用于选择了一条线后,就获取UIMenuController ,并创建UIMenuItem ,使用setTargetRect:CGRectMake

来设定UIMenuController 的大小。


For a menu controller to appear, a view that responds to at least one action message in the
UIMenuController’s menu items must be the first responder of the window – this is why you sent the
message becomeFirstResponder to the BNRDrawView before setting up the menu controller.
If you have a custom view class that needs to become the first responder, you must override
canBecomeFirstResponder. In BNRDrawView.m, override this method to return YES.

当menu controller显示之后,如果你要收到menu中的action,那么就必须成为window的first responder,

所以需要调用becomeFirstResponder;如果你是一个custom view,那就需要overridecanBecomeFirstResponder

并返回true.


- (BOOL)canBecomeFirstResponder
{
return YES;
}


You can build and run the application now, but when you select a line, the menu will not appear. When
being presented, the menu controller goes through each menu item and asks the first responder if it

implements the action message for that item. If the first responder does not implement that method,
then the menu controller will not show the associated menu item. If no menu items have their action
messages implemented by the first responder, the menu is not shown at all.
To get the Delete menu item (and the menu itself) to appear, implement deleteLine: in
BNRDrawView.m.
- (void)deleteLine:(id)sender
{
// Remove the selected line from the list of _finishedLines
[self.finishedLines removeObject:self.selectedLine];
// Redraw everything
[self setNeedsDisplay];
}
Build and run the application. Draw a line, tap on it, and then select Delete from the menu item.

只有first responder实现了menu item中对应的item, menu才会显示,否则不会显示。所以

需要在BNRDrawView.m.中实现- (void)deleteLine:(id)sender  action.


UILongPressGestureRecognizer
Let’s test out two other subclasses of UIGestureRecognizer: UILongPressGestureRecognizer and
UIPanGestureRecognizer. When you hold down on a line (a long press), that line will be selected and
you can then drag it around by dragging your finger (a pan).

现在学习UILongPressGestureRecognizer 和UIPanGestureRecognizer; 当长按手势触发时,对应的线段被选中,

并且可以被拖拽。


In this section, let’s focus on the long press recognizer. In BNRDrawView.m, instantiate a
UILongPressGestureRecognizer in initWithFrame: and add it to the BNRDrawView.
[self addGestureRecognizer:tapRecognizer];
UILongPressGestureRecognizer *pressRecognizer =
[[UILongPressGestureRecognizer alloc] initWithTarget:self
action:@selector(longPress:)];
[self addGestureRecognizer:pressRecognizer];


Now when the user holds down on the BNRDrawView, the message longPress: will be sent to
it. By default, a touch must be held 0.5 seconds to become a long press, but you can change the
minimumPressDuration of the gesture recognizer if you like.

当长按view后可以触发这个手势,默认是 0.5s,但你也可以修改这个值。


So far, you have worked with tap gestures. A tap is a simple gesture. By the time it is recognized, the
gesture is over, and the action message has been delivered. A long press, on the other hand, is a gesture
that occurs over time and is defined by three separate events.

之前你已经学习了tap手势,它比较简单,当tap手势被识别后,这个流程就结束了,对应的action消息也发给了相应的target;

但是对应长手势,它却要分成3个独立的事件。


For example, when the user touches a view, the long press recognizer notices a possible long press but
must wait to see whether the touch is held long enough to become a long press gesture.
Once the user holds the touch long enough, the long press is recognized and the gesture has begun.
When the user removes the finger, the gesture has ended.

比如,当用户touch view的时候,会判断它可能会是一个长手势,它会一直监听时间是否足够以触发长手势,

当确定是一个长手势事件后,这个action才会被发送。当用户手指从屏幕放开,这个手势就结束了。


Each of these events causes a change in the gesture recognizer’s state property. For instance, the
state of the long press recognizer described above would be UIGestureRecognizerStatePossible,
then UIGestureRecognizerStateBegan, and finally UIGestureRecognizerStateEnded.
When a gesture recognizer transitions to any state other than the possible state, it sends its action
message to its target. This means the long press recognizer’s target receives the same message when a

long press begins and when it ends. The gesture recognizer’s state allows the target to determine why it
has been sent the action message and take the appropriate action.
Here is the plan for implementing your action method longPress:. When the view receives
longPress: and the long press has begun, you will select the closest line to where the gesture
occurred. This allows the user to select a line while keeping the finger on the screen (which is
important in the next section when you implement panning). When the view receives longPress: and
the long press has ended, you will deselect the line.
In BNRDrawView.m, implement longPress:.
- (void)longPress:(UIGestureRecognizer *)gr
{
if (gr.state == UIGestureRecognizerStateBegan) {
CGPoint point = [gr locationInView:self];
self.selectedLine = [self lineAtPoint:point];
if (self.selectedLine) {
[self.linesInProgress removeAllObjects];
}
} else if (gr.state == UIGestureRecognizerStateEnded) {
self.selectedLine = nil;
}
[self setNeedsDisplay];
}
Build and run the application. Draw a line and then hold down on it; the line will turn green and be
selected and will stay that way until you let go.

 gesture recognizer内部有状态机,维护几个状态的改变,从UIGestureRecognizerStatePossible到UIGestureRecognizerStateBegan,

最后是UIGestureRecognizerStateEnded。

因此它的target会收到2个相同的消息,可以根据gesture的状态来判断是开始还是结束。



UIPanGestureRecognizer and Simultaneous
Recognizers
Once a line is selected during a long press, you want the user to be able to move that line around the
screen by dragging it with a finger. So you need a gesture recognizer for a finger moving around the
screen. This gesture is called panning, and its gesture recognizer subclass is UIPanGestureRecognizer.

想要实现拖拽功能,就需要使用UIPanGestureRecognizer。


Normally, a gesture recognizer does not share the touches it intercepts. Once it has recognized its
gesture, it “eats” that touch, and no other recognizer gets a chance to handle it. In your case, this is bad:
the entire pan gesture you want to recognize happens within a long press gesture. You need the long
press recognizer and the pan recognizer to be able to recognize their gestures simultaneously. Let’s see
how to do that.

一般情况下,如果一个手势发生,另外的手势就不可能触发了。但在这里却不行,因为你需要在long press发生过程中

触发pan手势。也就是说这2个手势同时的,并行的。


First, in the class extension in BNRDrawView.m, declare that BNRDrawView conforms to the
UIGestureRecognizerDelegate protocol. Then, declare a UIPanGestureRecognizer as a property so
that you have access to it in all of your methods.
@interface BNRDrawView () <UIGestureRecognizerDelegate>
@property (nonatomic, strong) UIPanGestureRecognizer *moveRecognizer;
@property (nonatomic, strong) NSMutableDictionary *linesInProgress;
@property (nonatomic, strong) NSMutableArray *finishedLines;
@property (nonatomic, weak) BNRLine *selectedLine;
@end

首先你需要BNRDrawView 实现UIGestureRecognizerDelegate 协议,其次你需要在BNRDrawView 中定义一个UIPanGestureRecognizer 属性。

In BNRDrawView.m, add code to initWithFrame: to instantiate a UIPanGestureRecognizer, set two of
its properties, and attach it to the BNRDrawView.
[self addGestureRecognizer:pressRecognizer];
self.moveRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self
action:@selector(moveLine:)];
self.moveRecognizer.delegate = self;
self.moveRecognizer.cancelsTouchesInView = NO;
[self addGestureRecognizer:self.moveRecognizer];

在BNRDrawView.m中,添加2个手势,并设置moveRecognizer的delegate和cancelsTouchesInView 2个属性。


There are a number of methods in the UIGestureRecognizerDelegate protocol, but you are only
interested in one – gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:.
A gesture recognizer will send this message to its delegate when it recognizes its gesture but
realizes that another gesture recognizer has recognized its gesture, too. If this method returns YES, the
recognizer will share its touches with other gesture recognizers.

UIGestureRecognizerDelegate 协议中有很多种方法,但你最关键的是要实现gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:;

当手势被识别但是它发现已经有另外一个手势已经建立的时候,就会给它的delegate发送消息。如果这个

方法返回的是true,那么这2个手势就可以共存了。


In BNRDrawView.m, return YES when the _moveRecognizer sends the message to its delegate.
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)other
{
if (gestureRecognizer == self.moveRecognizer) {
return YES;
}
return NO;
}


Now when the user begins a long press, the UIPanGestureRecognizer will be allowed to keep track of
this finger, too. When the finger begins to move, the pan recognizer will transition to the began state.
If these two recognizers could not work simultaneously, the long press recognizer would start, and the
pan recognizer would never transition to the began state or send its action message to its target.

这样这2个手势就可以共存了。


In addition to the states you have already seen, a pan gesture recognizer supports the changed state.
When a finger starts to move, the pan recognizer enters the began state and sends a message to its
target. While the finger moves around the screen, the recognizer transitions to the changed state
and sends its action message to its target repeatedly. Finally, when the finger leaves the screen, the
recognizer’s state is set to ended, and the final message is delivered to the target.

除了以上介绍的开始和结束状态,pan手势还有另外一个change的状态,在手势开始移动的时候,

状态是began,之后移动状态是change,当手指从屏幕移开,状态是ended,最后会发送final消息给

它的target。


Now you need to implement the moveLine: method that the pan recognizer sends its target. In
this implementation, you will send the message translationInView: to the pan recognizer. This
UIPanGestureRecognizer method returns how far the pan has moved as a CGPoint in the coordinate
system of the view passed as the argument. When the pan gesture begins, this property is set to the zero
point (where both x and y equal zero). As the pan moves, this value is updated – if the pan goes very
far to the right, it has a high x value; if the pan returns to where it began, its translation goes back to the
zero point.

现在你需要实现moveLine: 方法,这个是pan recognizer发送给它的target的action消息;在这个方法的实现中,

你需要向pan recognize发送translationInView:消息,这个方法会返回一个类型为CGPoint的值,它代表

手指移到的距离。开始pan的时候,这个值为0,移动过程中这个值为变化。


In BNRDrawView.m, implement moveLine:. Notice that because you will send the gesture recognizer a
method from the UIPanGestureRecognizer class, the parameter of this method must be a pointer to an
instance of UIPanGestureRecognizer rather than UIGestureRecognizer.

在 BNRDrawView.m的moveLine:方法中,因为你需要发送一个UIPanGestureRecognizer类的方法translationInView:,

所以moveLine:的参数类型必须是UIPanGestureRecognizer ;


- (void)moveLine:(UIPanGestureRecognizer *)gr
{
// If we have not selected a line, we do not do anything here
if (!self.selectedLine) {
return;
}
// When the pan recognizer changes its position...
if (gr.state == UIGestureRecognizerStateChanged) {
// How far has the pan moved?
CGPoint translation = [gr translationInView:self];
// Add the translation to the current beginning and end points of the line
CGPoint begin = self.selectedLine.begin;
CGPoint end = self.selectedLine.end;
begin.x += translation.x;
begin.y += translation.y;
end.x += translation.x;
end.y += translation.y;
// Set the new beginning and end points of the line
self.selectedLine.begin = begin;
self.selectedLine.end = end;
// Redraw the screen
[self setNeedsDisplay];
}
}

通过改变线段的起始位置来重绘线段。


Build and run the application. Touch and hold on a line and begin dragging – and you will immediately
notice that the line and your finger are way out of sync. This makes sense because you are adding the
current translation over and over again to the line’s original end points. You really need the gesture
recognizer to report the change in translation since the last time this method was called instead.

运行程序并拖拽,你会发现线段和手指并没有同步,原因是因为你把每次加上的距离是错误的,

起始你只需要加上每次变化了多少。


Fortunately, you can do this. You can set the translation of a pan gesture recognizer back to the zero
point every time it reports a change. Then, the next time it reports a change, it will have the translation
since the last event.

你可以使用setTranslation:CGPointZero,把移动到那个点设置为下次移动的原点。


Near the bottom of moveLine: in BNRDrawView.m, add the following line of code.
[self setNeedsDisplay];
[gr setTranslation:CGPointZero inView:self];
}
}
Build and run the application and move a line around. Works great!

现在一切都ok了。


Before moving on, let’s take a look at a property you set in the pan gesture recognizer –
cancelsTouchesInView. Every UIGestureRecognizer has this property and, by default, this property
is YES. This means that the gesture recognizer will eat any touch it recognizes so that the view will not
have a chance to handle it via the traditional UIResponder methods, like touchesBegan:withEvent:.

UIGestureRecognizer 有个属性cancelsTouchesInView,它设置为yes后,所有的touch事件都无法收到了。


Usually, this is what you want, but not always. In this case, the gesture that the pan recognizer
recognizes is the same kind of touch that the view handles to draw lines using the UIResponder
methods. If the gesture recognizer eats these touches, then users will not be able to draw lines.
When you set cancelsTouchesInView to NO, touches that the gesture recognizer recognizes also get
delivered to the view via the UIResponder methods. This allows both the recognizer and the view’s

UIResponder methods to handle the same touches. If you are curious, comment out the line that sets
cancelsTouchesInView to NO and build and run again to see the effects.

但在这里却不能设置为yes,因为你在拖拽的过程中,还需要画线的方法,而这个方法是UIResponder中move里实现的。

也就是说在pan手势的同时,view也会收到UIRponder的move action消息。


For the More Curious: UIMenuController and UIResponderStandardEditActions


The UIMenuController is typically responsible for showing the user an “edit” menu when it is
displayed; think of a text field or text view when you press and hold. Therefore, an unmodified menu
controller (one that you do not set the menu items for) already has default menu items that it presents,
like Cut, Copy, and other familiar options. Each item has an action message wired up. For example,
cut: is sent to the view presenting the menu controller when the Cut menu item is tapped.


All instances of UIResponder implement these methods, but, by default, these methods do not
do anything. Subclasses like UITextField override these methods to do something appropriate
for their context, like cut the currently selected text. The methods are all declared in the
UIResponderStandardEditActions protocol.


If you override a method from UIResponderStandardEditActions in a view, its menu item will
automatically appear in any menu you show for that view. This works because the menu controller
sends the message canPerformAction:withSender: to its view, which returns YES or NO depending on
whether the view implements this method.
If you want to implement one of these methods but do not want it to appear in the menu, you can
override canPerformAction:withSender: to return NO.
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender
{
if (action == @selector(copy:))
return NO;
// The superclass's implementation will return YES if the method is in the .m file
return [super canPerformAction:action withSender:sender];
}

UIMenuController 有默认的copy、cut等方法,如果对应的target有实现这些方法,那么对应的menuitem就

会显示。


For the More Curious: More on UIGestureRecognizer
We have only scratched the surface of UIGestureRecognizer; there are more subclasses, more
properties, and more delegate methods, and you can even create recognizers of your own. This
section will give you an idea of what UIGestureRecognizer is capable of, and then you can study the
documentation for UIGestureRecognizer to learn even more.

之前你只是看了UIGestureRecognizer的表面,现在让我们看看UIGestureRecognizer 的本质。


When a gesture recognizer is on a view, it is really handling all of the UIResponder methods, like
touchesBegan:withEvent:, for you. Gesture recognizers are pretty greedy, so they typically do not let
a view receive touch events or they at least delay the delivery of those events. You can set properties
on the recognizer, like delaysTouchesBegan, delaysTouchesEnded, and cancelsTouchesInView, to
change this behavior. If you need finer control than this all-or-nothing approach, you can implement
delegate methods for the recognizer.

gesture recognizer 对应的view已经可以处理UIResponder 方法,比如touchesBegan:withEvent:;

但gesture recognizer非常贪婪,它往往不让view收到UIResponder消息, 可以通过delaysTouchesBegan, delaysTouchesEnded, and cancelsTouchesInView

等方法实现目的。如果你想要更好的控制,你就需要实现recognizer的delegate方法。


At times, you may have two gesture recognizers looking for very similar gestures. You can chain
recognizers together so that one is required to fail for the next one to start using the method
requireGestureRecognizerToFail:.

有可能有2种手势同时触发,为了避免这种情况发生,使用requireGestureRecognizerToFail:


One thing you must understand to master gesture recognizers is how they interpret their state. Overall,
there are seven states a recognizer can enter:
• UIGestureRecognizerStatePossible • UIGestureRecognizerStateFailed
• UIGestureRecognizerStateBegan • UIGestureRecognizerStateCancelled
• UIGestureRecognizerStateChanged • UIGestureRecognizerStateRecognized
• UIGestureRecognizerStateEnded

gesture recognizer有7种状态。


Most of the time, a recognizer will stay in the possible state. When a recognizer recognizes its gesture,
it goes into the began state. If the gesture is something that can continue, like a pan, it will go into and
stay in the changed state until it ends. When any of its properties change, it sends another message to
its target. When the gesture ends (typically when the user lifts the finger), it enters the ended state.

大多是情况下,recognizer 处于possible状态,当它识别到手势的时候,会进入began状态,如果一个手势

可以持续,比如pan,它就会进入changed状态。任何状态改变都会想它的target发送消息,直到手势结束(比如手指从屏幕移开)。


Not all recognizers begin, change, and end. For gesture recognizers that pick up on a discrete gesture
like a tap, you will only ever see the recognized state (which has the same value as the ended state).
Finally, a recognizer can be cancelled (by an incoming phone call, for example) or fail (because no
amount of finger contortion can make the particular gesture from where the fingers currently are).
When these states are transitioned to, the action message of the recognizer is sent, and the state
property can be checked to see why.

并不是每个手势都是有上述三种状态的,比如tap手势,它只有recognized状态(也就是当它识别的时候就结束了。。。)

最后,手势也可以是cancelled或者fail(当无法根据手指的动作确定手势)


The three built-in recognizers you did not implement in this chapter are UIPinchGestureRecognizer,
UISwipeGestureRecognizer, and UIRotationGestureRecognizer. Each of these have properties that
allow you to fine-tune their behavior. The documentation will show you the way.

本书还有另外的三种手势没讲:UIPinchGestureRecognizer UISwipeGestureRecognizer UIRotationGestureRecognizer,

自己去看文档吧


Finally, if there is a gesture you want to recognize that is not implemented by the built-in subclasses
of UIGestureRecognizer, you can subclass UIGestureRecognizer yourself. This is an intense
undertaking and outside the scope of this book. You can read the Subclassing Notes in the
UIGestureRecognizer documentation to learn what is required.

另外你也可以继承UIGestureRecognizer 实现自己的手势。


Silver Challenge: Mysterious Lines
There is a bug in the application. If you tap on a line and then start drawing a new one while the menu
is visible, you will drag the selected line and draw a new line at the same time. Fix this bug.



Gold Challenge: Speed and Size
Piggy-back off of the pan gesture recognizer to record the velocity of the pan when you are drawing
a line. Adjust the thickness of the line being drawn based on this speed. Make no assumptions about
how small or large the velocity value of the pan recognizer can be. (In other words, log a variety of
velocities to the console first.)

根据移动速度来绘制线的粗细。


Mega-Gold Challenge: Colors
Have a three-finger swipe upwards bring up a panel that shows some colors. Selecting one of those
colors should make any lines you draw afterwards appear in that color. No extra lines should be
drawn by putting up that panel – or at least any lines drawn should be immediately deleted when the
application realizes that it is dealing with a three-finger swipe.

使用三个手指调出颜色栏,选中颜色,用来绘制接下来的线段。














0 0
原创粉丝点击