美文网首页iOS开发
UIGestureRecognizer and UIMenuCo

UIGestureRecognizer and UIMenuCo

作者: ilaoke | 来源:发表于2015-08-08 20:44 被阅读404次

    UIGestureRecognizer and UIMenuController

    UIGestureRecognizer有许多子类,响应不同的手势。

    为UIGestureRecognizer实例指定target-action,并将UIGestureRecognizer实例绑定到view上,当UIGestureRecognizer实例识别view上的某种手势后,他会发送指定的action消息到target。

    所有的UIGestureRecognizer action 消息都是以下的形式:

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

    Gesture recognizer拦截view上的touch事件,因此一个有gesture recognizer的view可能不会收到UIResponder的消息,如touchesBegan:withEvent:

    UITapGestureRecognizer

    UITapGestureRecognizer是UIGestureRecognizer的子类。
    在BKDrawView.m,在初始化方法中为其绑定监听双击的gesture recognizer,gesture recognizer的target是view本身,action为doubleTap,所以当双击事件被监听到,会执行target的doubleTap方法。

    - (instancetype)initWithFrame:(CGRect)frame{
        self = [super initWithFrame:frame];
        
        if (self) {
            self.linesInProgress = [[NSMutableDictionary alloc] init];
            self.finishedLines = [[NSMutableArray alloc] init];
            self.backgroundColor = [UIColor grayColor];
            // 启用multiple touches
            self.multipleTouchEnabled = YES;
            
            // 创建监听双击的gesture recognizer
            UITapGestureRecognizer *doubleTapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(doubleTap)];
            doubleTapRecognizer.numberOfTapsRequired = 2;
            // 将gesture recognizer关联到view上
            [self addGestureRecognizer:doubleTapRecognizer];
        }
        return self;
    }
    - (void)doubleTap:(UIGestureRecognizer *)gr{
        NSLog(@"Recognized Double Tap");
        
        [self.linesInProgress removeAllObjects];
        [self.finishedLines removeAllObjects];
        [self setNeedsDisplay];
    }
    

    doubleTap方法的参数是gesture recognizer,并且是这个gesture recognizer发送了doubleTap消息到target.

    此时双击view,会显示如下log:

    2015-08-06 00:20:46.639 TouchTracker[3301:70b] touchesBegan:withEvent:
    2015-08-06 00:20:46.767 TouchTracker[3301:70b] Recognized Double Tap
    2015-08-06 00:20:46.768 TouchTracker[3301:70b] touchesCancelled:withEvent:
    

    Gesture recognizer检查触摸事件,来判断是否其监听的手势发生了。在gesture recognizer识别手势之前,这些UIResponder 消息仍会正常发送给view。

    当touch事件发生,而gesture recognizer还不能识别为其监听的手势时,则touchesBegan:withEvent: 会被发送给view,而当gesture recognizer识别了手势,UIResponder消息不再发送给view,为了告诉view touch事件被接管,会发送touchesCancelled:withEvent:给view。

    为阻止这种情况发生,可以让gesture recognizer延迟发送touchesBegan:withEvent: 给view,即当touch不再可能被识别成特定的gesture了,再发送touchesBegan:withEvent: 消息给view。

    // 当touch不再可能被识别为double tap gesture了,再发送touches began
    doubleTapRecognizer.delaysTouchesBegan = YES;
    

    Multiple Gesture Recognizer

    再为BKDrawView添加监听单击的gesture recognizer:

    - (instancetype)initWithFrame:(CGRect)frame{
        self = [super initWithFrame:frame];
        
        if (self) {
            self.linesInProgress = [[NSMutableDictionary alloc] init];
            self.finishedLines = [[NSMutableArray alloc] init];
            self.backgroundColor = [UIColor grayColor];
            // 启用multiple touches
            self.multipleTouchEnabled = YES;
            
            // 创建监听双击的gesture recognizer
            UITapGestureRecognizer *doubleTapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(doubleTap:)];
            doubleTapRecognizer.numberOfTapsRequired = 2;
            // 当touch不再可能被识别为double tap gesture了,再发送touches began
            doubleTapRecognizer.delaysTouchesBegan = YES;
            // 将gesture recognizer关联到view上
            [self addGestureRecognizer:doubleTapRecognizer];
            
            // 添加监听单击的gesture recognizer
            UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tap:)];
            tapRecognizer.delaysTouchesBegan = YES;
            [self addGestureRecognizer:tapRecognizer];
            
        }
        return self;
    }
    - (void)doubleTap:(UIGestureRecognizer *)gr{
        NSLog(@"Recognized Double Tap");
        
        [self.linesInProgress removeAllObjects];
        [self.finishedLines removeAllObjects];
        [self setNeedsDisplay];
    }
    - (void)tap:(UIGestureRecognizer *)gr{
        NSLog(@"Recognized tap");
    }
    

    现在view上有两个gesture recognizer,双击view,既会触发监听单击的gesture recognizer,也会触发监听双击的gesture recognizer,此时需要在这些gesture recognizers之间添加dependency,就像在说”你等下,这个gesture可能是我的“。

    // 单击gesture recognizer要等待双击gesture recognizer失败再触发
    [tapRecognizer requireGestureRecognizerToFail:doubleTapRecognizer];
    

    UIMenuController

    UIMenuController 包含一组UIMenuItem对象,每个menu item有一个title和action(action消息被发送给window的first responder)。

    一个application只有一个UIMenuController对象,当要显示menu controller时,要为其设置menu items,指定一个距形去显示,并设置为可见。

    - (void)tap:(UIGestureRecognizer *)gr{
        NSLog(@"Recognized tap");
        // 找到当前手势在view中的坐标
        CGPoint point = [gr locationInView:self];
        self.selectedLine = [self lineAtPoint:point];
        
        if (self.selectedLine) {
            // 将view本身设置为window的first responder
            [self becomeFirstResponder];
            
            // 获得menu controller单例
            UIMenuController *menu = [UIMenuController sharedMenuController];
            // 创建menu item,并指定title和action
            UIMenuItem *deleteItem = [[UIMenuItem alloc] initWithTitle:@"Delete" action:@selector(deleteLine:)];
            // 为menu controller 设置menu items
            menu.menuItems = @[deleteItem];
            // 为menu controller指定距形
            [menu setTargetRect:CGRectMake(point.x, point.y, 2, 2) inView:self];
            // 将menu controlelr设置为可见
            [menu setMenuVisible:YES animated:YES];
        }else{
            // 隐藏menu controller
            [[UIMenuController sharedMenuController] setMenuVisible:NO animated:YES];
        }
        
        [self setNeedsDisplay];
    }
    

    上面代码中,首先将view自身设置为window的first responder,一个自定义view要成为first responder,必须重写canBecomeFirstResponder方法,并返回YES.

    // 重写canBecomeFirstResponder方法,并返回YES,使得当前VIEW可以成为first responder
    - (BOOL)canBecomeFirstResponder{
        return YES;
    }
    

    现在运行程序,你会发现menu并没有出现,因为first responder没有menu item对应的action方法,添加deleteLine方法:

    - (void)deleteLine:(id)sender{
        [self.finishedLines removeObject:self.selectedLine];
        // Redraw everything
        [self setNeedsDisplay];
    }
    

    UILongPressGestureRecognizer

    为BKDrawView添加long press gesture recognizer,默认touch持续0.5秒即为long press,可以修改minimumPressDuration来改变持续时间。

    // 添加监听长按的gesture recognizer
    UILongPressGestureRecognizer *pressRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longPress:)];
    [self addGestureRecognizer:pressRecognizer];
    

    不像tap这种简单的gesture recognizer,long press gesture recognizer有三种state:UIGestureRecognizerStatePossible, UIGestureRecognizerStateBegan, UIGestureRecognizerStateEnded。
    当gesture recognizer状态变成非possible时,就会发送action消息到target,所以在press gesture的开始和结束状态,target都会收到action消息。

    下面当长按屏幕时,选中最近的一条线,当长按结束,释放选中的线:

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

    UIPanGestureRecognizer

    当用户长按线条,然后移动线条,这个动作就叫panning。

    通常gesture recognizer不分享其捕获的touch,一旦识别为了gesture,这些touch不会再被其他gesture recognizer处理。然而pan gesture发生在long press gesture中,需要这两种gesture recognizer能够同时识别gesture。

    为了实现这种共享touch,需要实现UIGestureRecognizerDelegate protocol的
    gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:方法,当此方法返回YES,gesture recognizer之间可以分享touches。

    pan gesture recognizer支持changed state,当手指开始移动,pan recognizer进入began state并发送action消息到target,当手指继续移动,pan gesture recognizer进入changed state并发送action消息到target。最后,当手指离开屏幕,pan gesture recognizer进入ended state并发送最后一次action消息到target。

    @interface BKDrawView () <UIGestureRecognizerDelegate>
    
    //@property (nonatomic, strong) BKLine *currentLine;
    // 保存当前的多个line
    @property (nonatomic, strong) NSMutableDictionary *linesInProgress;
    @property (nonatomic, strong) NSMutableArray *finishedLines;
    
    @property (nonatomic, weak) BKLine *selectedLine;
    @property (nonatomic, strong) UIPanGestureRecognizer *moveRecognizer;
    
    @end
    
    @implementation BKDrawView
    
    - (instancetype)initWithFrame:(CGRect)frame{
        self = [super initWithFrame:frame];
        
        if (self) {
            self.linesInProgress = [[NSMutableDictionary alloc] init];
            self.finishedLines = [[NSMutableArray alloc] init];
            self.backgroundColor = [UIColor grayColor];
            // 启用multiple touches
            self.multipleTouchEnabled = YES;
            
            // 创建监听双击的gesture recognizer
            UITapGestureRecognizer *doubleTapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(doubleTap:)];
            doubleTapRecognizer.numberOfTapsRequired = 2;
            // 当touch不再可能被识别为double tap gesture了,再发送touches began
            doubleTapRecognizer.delaysTouchesBegan = YES;
            // 将gesture recognizer关联到view上
            [self addGestureRecognizer:doubleTapRecognizer];
            
            // 添加监听单击的gesture recognizer
            UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tap:)];
            tapRecognizer.delaysTouchesBegan = YES;
            // 单击gesture recognizer要等待双击gesture recognizer失败再触发
            [tapRecognizer requireGestureRecognizerToFail:doubleTapRecognizer];
            [self addGestureRecognizer:tapRecognizer];
            
            // 添加监听长按的geture recognizer
            UILongPressGestureRecognizer *pressRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longPress:)];
            [self addGestureRecognizer:pressRecognizer];
            
            // pan gesture recognizer
            self.moveRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(moveLine:)];
            self.moveRecognizer.delegate = self;
            self.moveRecognizer.cancelsTouchesInView = NO;
            [self addGestureRecognizer:self.moveRecognizer];
            
        }
        return self;
    }
    
    - (void)moveLine:(UIPanGestureRecognizer *)pgr{
        if (!self.selectedLine) {
            return;
        }
        
        // When the pan recognizer changes its positon
        if (pgr.state == UIGestureRecognizerStateChanged) {
            // translationInView方法返回,pan gesture已经移动了多远
            CGPoint translation = [pgr translationInView:self];
            
            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;
            
            self.selectedLine.begin = begin;
            self.selectedLine.end = end;
            
            [self setNeedsDisplay];
            // 重置translationInVew为零,使得从上一次change事件后从0开始计算translation
            [pgr setTranslation:CGPointZero inView:self];
        }
    }
    
    - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer{
        if (gestureRecognizer == self.moveRecognizer) {
            return YES;
        }
        return NO;
    }
    

    UIPanGestureRecognizer的translationInView:方法返回pan gesture移动了多远,返回的坐标CGPoint是在X,Y轴上移动距离的值。
    每个UIGestureRecognizer都有cancelsTouchesInView属性,默认值是YES,意思是gesture recognizer会吃掉其识别的touch event,从而不会触发UIResponder的方法,如touchesBegan:withEvent:,将其设置为NO,从而使得touchesMoved:withEvent:可以被执行,因为gesture recognizer是拦截器,他控制了是否执行UIResponder的方法。

    UIResponderStandardEditActions

    UIResponderStandardEditActions协议声明了UIMenuController中的action方法,如果view实现了这些方法,当UIMenuController显示时,就会显示对应的menu item。
    判断view是否实现了某方法,由canPerformAction:withSender:方法来判断,menu controller 会发送该消息给view,默认是当view实现了某方法,就返回YES,否则返回NO,我们可以重写该方法。

    - (BOOL)canPerformAction:(SEL)action withSender:(id)sender{
        //return [super canPerformAction:action withSender:sender];
        return YES;
    }
    

    除了上面所讲的gesture recognizer,还有三个内置的gesture recognizer: UIPinchGestureRecognizer, UISwipeGestureRecognizer, UIRotationGestureRecognizer


    本文是对《iOS Programming The Big Nerd Ranch Guide 4th Edition》第十三章的总结。

    相关文章

      网友评论

        本文标题:UIGestureRecognizer and UIMenuCo

        本文链接:https://www.haomeiwen.com/subject/nctiqttx.html