你的位置:首页 > 操作系统

[操作系统]iOS Programming UIGestureRecognizer and UIMenuController


iOS  Programming  UIGestureRecognizer and UIMenuController

A UIGestureRecognizer intercepts touches that are on their way to being handled by a view.

一个UIGestureRecognizer拦截touches 在他们被一个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.

在内部的SDK中,有一些特殊的手势类型。

1  UIGestureRecognizer Subclasses

You do not instantiate UIGestureRecognizer itself. Instead, there are a number of subclasses of UIGestureRecognizer, and each one is responsible for recognizing a particular gesture.

你并不会实例化UIGestureRecognizer自身,而是有一些UIGestureRecognizer的子类,这些子类负责鉴别一个特殊的手势。

To use an instance of a UIGestureRecognizer subclass, you give it a target-action pair and attach it to a view.

为了实例化UIGestureRecognizer子类,你给了它一个target-action pair 并把它与一个view 联系在一起。

Whenever the gesture recognizer recognizes its gesture on the view, it will send the action message to its target.

当一个gesture recognizer 识别出了在view 上的手势,它将会发送动作信息给它的target。

- (void)action:(UIGestureRecognizer *)gestureRecognizer;

When recognizing a gesture, the gesture recognizer intercepts the touches destined for the view

当鉴别出一个手势后,它的gesture recognizer 为指定的view解析这个touches。

Thus, a view with gesture recognizers may not receive the typical UIResponder messages like touchesBegian:withEvent:.

因此一个有gesture recognizers的view 可能不接受典型的UIResponder 消息,比如touchesBegian:withEvent

2 Detecting Taps with UITapGestureRecognizer

The first UIGestureRecognizer subclass you will use is UITapGestureRecognizer.

第一个你将会使用的UIGestureRecognizer子类是UITapGestureRecognizer。

When the user taps the screen twice, all of the lines on the screen will be cleared.

当用户点击屏幕两次的时候,所有的线就会被清除掉。

 

In BNRDrawView.m, instantiate a UITapGestureRecognizer that requires two taps to fire in initWithFrame:.

UITapGestureRecognizer *doubleTapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self

action:@selector(doubleTap:)]; doubleTapRecognizer.numberOfTapsRequired = 2;

[self addGestureRecognizer:doubleTapRecognizer];

 

- (void)doubleTap:(UIGestureRecognizer *)gr

{
NSLog(@"Recognized Double Tap");

[self.linesInProgress removeAllObjects]; [self.finishedLines removeAllObjects]; [self setNeedsDisplay];

}

Notice that the argument to the action method for a gesture recognizer is the instance of UIGestureRecognizer that sent the message.

注意到一个gesture recognizer  的action方法的参数 是那个发送消息的UIGestureRecognizer实例。

In the case of a double tap, you do not need any information from the recognizer。

在这种两个tap 的情况下,你不需要从recognizer的任何消息。

 

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.

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.

当一个touch 开始和结束在很短的一段时间内完成时,一个tap gesture recognizer 被识别,UITapGestureRecognizer 不能声明这个touch 就是一个tap,touchesBegan:withEvent:也被发送给这个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 sent to the view for that particular touch.

一旦一个tap 最终被识别,gesture recognizer 将会声明包含在这个tap 中的touch 。对于这个特殊的touch也不会再发送给这个view 。

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.

为了与这个touch交流,touchesCancelled:withEvent将会被发送给这个view ,以及包含这个UITouch 实例的NSSet。

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.

为了阻止这个红色的点临时出现,你能告诉UIGestureRecognizer延迟发送touchesBegan:withEvent给它的view,如果它仍然可能手势被识别。

 

doubleTapRecognizer.delaysTouchesBegan = YES;

 

 UITapGestureRecognizer *doubleTapGestureRecognizer=[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(doubleTap:)];

        doubleTapGestureRecognizer.numberOfTapsRequired=2;

        doubleTapGestureRecognizer.delaysTouchesBegan=YES;

        [self addGestureRecognizer:doubleTapGestureRecognizer];

 

-(void)doubleTap:(UIGestureRecognizer*)gestureRecognzier{

    NSLog(@"Recognizer Double Tap");

    [self.linesInProgress removeAllObjects];

    [self.finishedLines removeAllObjects ];

    [self setNeedsDisplay];

}

 

3.multiple gesture Recognizers 

add another gesture recognizer that allows the user to select a line.

 

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.

当你有多个gesture recognizers时,经常看到让一个recognizer 发出激活 当你实际上想让另一个gesture recognizer 来处理这个工作

 

In these cases, you set up dependencies between recognizers that say, "Just wait a moment before you fire, because this gesture might be mine!"

在这种情况下,你在两个recognzier之间设置依赖关系:"在你fire之前先等一下,因为这个手势可能是我的。"

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.

在它假设是一个single tap而不是一个double tap 的第一个之前,tapRecognizer需要等待doubleTapRecognizer失败。

 

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.

@property (nonatomic, weak) BNRLine *selectedLine;

 

The point you are interested in, of course, is where the tap occurred. You can easily get this information.

你感兴趣的位置应该是tap 的位置。你可以轻松的得到这个消息。

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.

每一个UIGestureRecognizer有一个locationInView方法。把这个信息给gesture recognizer将会给你提供你手势发生的在给定view 坐标系中得坐标。

 

UITapGestureRecognizer *singleTapGestureRecognizer=[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(singleTap:)];

        singleTapGestureRecognizer.delaysTouchesBegan=YES;

        [singleTapGestureRecognizer requireGestureRecognizerToFail:doubleTapGestureRecognizer];

        [self addGestureRecognizer:singleTapGestureRecognizer];

 

-(void)singleTap:(UIGestureRecognizer*)gestureRecognzier{

    NSLog(@"Recongizer Single Tap");

    CGPoint point=[gestureRecognzier locationInView:self];

    self.selectedLine=[self lineAtPoint:point];

    [self setNeedsDisplay];

     

}

 

   if (self.selectedLine) {

        [[UIColor greenColor] set];

        [self strokeLine:self.selectedLine];

    }

 

 

-(LKLine*)lineAtPoint:(CGPoint) point{

    for (LKLine *line in self.finishedLines) {

        CGPoint start=line.begin;

        CGPoint end=line.end;

         

        for (float t=0.0 ; t<=1.0; t+=0.5) {

            float x=start.x+t*(end.x-start.x);

            float y=start.y+t*(end.y-start.y);

             

            if (hypot(x-point.x, y-point.y)<20.0) {

                return line;

            }

        }

    }

    return nil;

}

4. UIMenuController 

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

有一个内部构建的类来提供这种菜单,称为UIMenuController

A menu controller has a list of UIMenuItem objects and is presented in an existing view.

一个menu Controller 有一列UIMenuItem对象,被展现在已经存在的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)

每个item 有一个标题(显示在menu上)和一个action (消息它会发送给window 的first responder)

There is only one UIMenuController per application.

每个应用只有一个UIMenuController 。

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.

当你希望展现这个实例时,你用menu items 填充它,给它一个矩形来展现并设置它可以看见。

 

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.

 

 

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.

为了让一个menu Controller 出现,那个至少响应一个action信息的UIMenuController的菜单item 的view 将会称为first responder of the  window .

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.

如果你有一个custom view 类需要称为first responder ,你必须重写canBecomeFirstResponder方法。

- (BOOL)canBecomeFirstResponder

{
return YES;

}

 

 

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.

当将被展现时,menu controller 遍历每个menu item ,询问first responder 看看是否实现了那个item 的action message .

If the first responder does not implement that method, then the menu controller will not show the associated menu item.

如果first responder 没有实现那个方法,那么menu controller 将不会显示相关的menu item.

If no menu items have their action messages implemented by the first responder, the menu is not shown at all.

如果没有menu items 有他们的action 信息被first responder 实现,它的menu 将根本不会出现。

 

 if (self.selectedLine) {

        [self becomeFirstResponder];

        UIMenuController *menuController=[UIMenuController sharedMenuController];

        UIMenuItem *deleteMenuItem=[[UIMenuItem alloc] initWithTitle:@"Delete" action:@selector(deleteLine:)];

        menuController.menuItems=@[deleteMenuItem];

         

        [menuController setTargetRect:CGRectMake(point.x, point.y, 2, 2) inView:self];

        [menuController setMenuVisible:YES animated:YES];

    }else{

        [[UIMenuController sharedMenuController ] setMenuVisible:NO animated:YES];

    }

 

-(BOOL)canBecomeFirstResponder{

    return YES;

}

-(void)deleteLine:(id)sender{

    [self.finishedLines removeObject:self.selectedLine];

    [self setNeedsDisplay];

}

 

5 UILongPressGestureRecognizer

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).

let's focus on the long press recognizer. In BNRDrawView.m, instantiate a UILongPressGestureRecognizer in initWithFrame: and add it to the BNRDrawView.

 

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.

现在你可以长按,那么longPress 消息就会被发送。默认情况下,一个touch必须保持0.5秒,但是你能够改变手势识别的最小时间。

A tap is a simple gesture. By the time it is recognized, the gesture is over, and the action message has been delivered.

一个tap是简单地手势。当它被识别后,手势就结束了,action消息就会被发出。

A long press, on the other hand, is a gesture that occurs over time and is defined by three separate events.

一个长按,是一个发生在过程的手势,被定义了三个分别的事件。

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.

当一个用户触摸一个view,long press recognizer 注意到一个可能的long press,但是必须等到看看这个touch 是否维持了做够长的时间成为一个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.

一旦用户hold 足够长时间的touch,long press 就会被识别,gesture 便开始了,当用户移除手指,这个手势就结束了。 

Each of these events causes a change in the gesture recognizer's state property.

这些时间的每一过程都会导致一个gesture recognzier的状态属性的改变。

the state of the long press recognizer described above would be UIGestureRecognizerStatePossible, then UIGestureRecognizerStateBegan, and finally UIGestureRecognizerStateEnded.

long press recognizer的状态可能是UIGestureRecognizerStatePossible,接着UIGestureRecognizerStateBegan,最后UIGestureRecognizerStateEnded。

When a gesture recognizer transitions to any state other than the possible state, it sends its action message to its target.

当处理识别到possible state,gesture recognizer 将会发送一个action 消息给target。 

This means the long press recognizer's target receives the same message when a long press begins and when it ends.

也就是说当 a long press开始和结束的时候,long press recognizer's target都会接受到相同的信息。

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.

当view接受到longPress消息时,他就会选择最近选择的line。

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).

这允许用户选择一个line,当手指一直在屏幕上的时候。

When the view receives longPress: and the long press has ended, you will deselect the line.

当view 收到longPress 消息时,long press 结束的时候,你会取消选择那个line。

 

-(void)longPress:(UIGestureRecognizer*)gestureRecognzier{

    if (gestureRecognzier.state==UIGestureRecognizerStateBegan) {

        CGPoint point=[gestureRecognzier locationInView:self];

        self.selectedLine=[self lineAtPoint:point];

         

        if (self.selectedLine) {

            [self.linesInProgress removeAllObjects];

        }

    }else if( gestureRecognzier.state==UIGestureRecognizerStateEnded ){

        self.selectedLine=nil;

    }

    [self setNeedsDisplay];

}

 

  UILongPressGestureRecognizer *longPressGestureRecognizer=[[UILongPressGestureRecognizer alloc]initWithTarget:self action:@selector(longPress:)];

        [self addGestureRecognizer:longPressGestureRecognizer];

6 UIPanGestureRecognizer and Simultaneous Recognizers

So you need a gesture recognizer for a finger moving around the screen.

为了手指能在屏幕上移动,你需要一个gesture recognizer .

This gesture is called panning, and its gesture recognizer subclass is UIPanGestureRecognizer.

这个手势成为panning。 它的gesture recognizer subclass 是UIPanGestureRecognizer。

Normally, a gesture recognizer does not share the touches it intercepts.

一般的,gesture recognizer并不分享这些被拦截的touches。

Once it has recognized its gesture, it "eats" that touch, and no other recognizer gets a chance to handle it.

一旦它被识别它的手势,它会"吃进"这个touch,没有其他的recognizer 有机会处理它。

In your case, this is bad: the entire pan gesture you want to recognize happens within a long press gesture.

在这种情况下,非常不好:真个的pan gesture 你想识别的, 整个的发生在long press gesture下。

You need the long press recognizer and the pan recognizer to be able to recognize their gestures simultaneously.

你需要long press recognizer and the pan recognizer能够同时识别出他们的手势。

 

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;

 

add code to initWithFrame: to instantiate a UIPanGestureRecognizer, set two of its properties, and attach it to the BNRDrawView.

 

self.moveRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(moveLine:)];

self.moveRecognizer.delegate = self; self.moveRecognizer.cancelsTouchesInView = NO; [self addGestureRecognizer:self.moveRecognizer];

 

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.

一个gesture recognizer 将会发送

gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer消息给它的委托当它识别出它的手势但是也了解到另一个gesture recognize 也已经识别出这个手势了。

If this method returns YES, the recognizer will share its touches with other gesture recognizers.

如果这个手势返回YES,那么recognizer 将会分享它的touches 和其他的gesture recognizers .

 

- (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.

现在当用户开始长按,UIPanGestureRecognizer将会被允许跟踪手指。

When the finger begins to move, the pan recognizer will transition to the began state.

如果手指开始移动,pan recognizer将会转成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.

 

a pan gesture recognizer supports the changed state.

一个pan gesture recognizer 支持改变state。

When a finger starts to move, the pan recognizer enters the began state and sends a message to its target.

当手指开始移动时, pan recognizer  进入began state并发送一个消息给它的target。

While the finger moves around the screen, the recognizer transitions to the changed state and sends its action message to its target repeatedly.

当手指在屏幕上移动时,recognizer 转移成 changed state 并反复的 发送action message 到target 。 

Finally, when the finger leaves the screen, the recognizer's state is set to ended, and the final message is delivered to the target.

最后 当手指离开屏幕时,recognzier 的state 设置为ended 。最后的消息被发送给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.

在这个实现中,你将发送消息 translationInView给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.

UIPanGestureRecognizer方法会返回这个pan 作为CGPoint 在给定参数的view中得坐标系中移动了多少。 

When the pan gesture begins, this property is set to the zero point (where both x and y equal zero).

当pan gesture 开始的时候 ,这个属性设置为 zero point。

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.

如果pan 移动了,这个value就会被更新。

 

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.

 

???

 

- (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]; }

}

 

because you are adding the current translation over and over again to the line's original end points.

你在添加现在的translation 一遍又一遍的到直线最开始的位置。

You really need the gesture recognizer to report the change in translation since the last time this method was called instead.

你其实需要gesture recognizer 报告自从上次方法被调用后在translation  的变化.

You can set the translation of a pan gesture recognizer back to the zero point every time it reports a change.

你可以在每次它报告一个change后可以设置pan gesture recognzier 的translation 返回到零。

        [panGestureRecognizer setTranslation:CGPointZero inView:self];

 

let's take a look at a property you set in the pan gesture recognizer – cancelsTouchesInView.

让我们看一看你设置的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:.

这就意味 gesture recognizer 会吃进任何它识别的touch,所以view 将不会有机会通过传统的UIResponder 方法像touchesBegan:withEvent处理了。

Usually, this is what you want, but not always.

一般情况下这是你想要的结果。

 

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.