【问题标题】:Swipe after long press长按后滑动
【发布时间】:2014-09-22 15:16:02
【问题描述】:

好的,我正在帮助将安卓游戏转换为 iOS。该游戏基于 2048,但使用字母而不是数字。我有很多工作,但仍在学习 Objective C/iOS 的怪癖。到目前为止,我的瓷砖/网格正在工作,运动正在工作等,但我需要一些帮助。目标是允许用户长按一个拼贴来选择它,然后将他们的手指滑到相邻的拼贴上以开始拼写一个单词。我已经实现了长按部分,但我对如何让它长按然后滑动有点茫然。最重要的是,我已经有了一个允许用户移动图块的滑动。在这里搜索时,我看到了关于子类化的建议,所以我想我需要子类化 UISwipeGestureRecognizer 方法。我已经安装了同时手势识别器,但不知道从哪里开始。

所以,有几个问题。

  1. 最好的方法是什么?实现每个UISwipeGestureRecognizer的子类?

  2. 我当前的滑动检测会干扰吗? (现在滑动本身会在滑动方向上移动图块)

  3. 我想我需要做一个(如果长按)然后激活子类滑动方法?

  4. 回答上述问题的任何示例都会有很大帮助。我不是要你为我做这件事,但至少可以为我指明一个大致的方向。谢谢!

代码如下。

//  Grid.m
#import "Grid.h"
#import "Tile.h"

- (void)didLoadFromCCB {
    // listen for swipes to the left
    UISwipeGestureRecognizer * swipeLeft= [[UISwipeGestureRecognizer alloc]initWithTarget:self action:@selector(swipeLeft)];
    swipeLeft.direction = UISwipeGestureRecognizerDirectionLeft;
    [[[CCDirector sharedDirector] view] addGestureRecognizer:swipeLeft];

    // listen for swipes to the right
    UISwipeGestureRecognizer * swipeRight= [[UISwipeGestureRecognizer alloc]initWithTarget:self action:@selector(swipeRight)];
    swipeRight.direction = UISwipeGestureRecognizerDirectionRight;
    [[[CCDirector sharedDirector] view] addGestureRecognizer:swipeRight];

    // listen for swipes up
    UISwipeGestureRecognizer * swipeUp= [[UISwipeGestureRecognizer alloc]initWithTarget:self action:@selector(swipeUp)];
    swipeUp.direction = UISwipeGestureRecognizerDirectionUp;
    [[[CCDirector sharedDirector] view] addGestureRecognizer:swipeUp];

    // listen for swipes down
    UISwipeGestureRecognizer * swipeDown= [[UISwipeGestureRecognizer alloc]initWithTarget:self action:@selector(swipeDown)];
    swipeDown.direction = UISwipeGestureRecognizerDirectionDown;
    [[[CCDirector sharedDirector] view] addGestureRecognizer:swipeDown];

    // listen for long press
    UILongPressGestureRecognizer *longpress = [[UILongPressGestureRecognizer alloc]initWithTarget:self action:@selector(onLongPress:)];
    [longpress setMinimumPressDuration:0.5];
    [[[CCDirector sharedDirector] view] addGestureRecognizer:longpress];
}

- (void)swipeLeft {
    [self move:ccp(-1, 0)];
}
- (void)swipeRight {
    [self move:ccp(1, 0)];
}
- (void)swipeDown {
    [self move:ccp(0, -1)];
}
- (void)swipeUp {
    [self move:ccp(0, 1)];
}

// detect longpress, convert to NodeSpace and check if touch location is within tile boundingbox. If yes, set background white, text black.
- (void)onLongPress:(UILongPressGestureRecognizer *) recognizer {
    CGPoint touchPoint = [[CCDirector sharedDirector] convertToGL:[recognizer locationInView:[recognizer view]]];
    touchPoint = [self convertToNodeSpace:touchPoint];

    if (recognizer.state == UIGestureRecognizerStateBegan) {
        for (Tile *tile in self.children) {
            if([tile isKindOfClass:[Tile class]]) {

                CGRect tileBoundingBox = tile.boundingBox;
                if (CGRectContainsPoint(tileBoundingBox, touchPoint)) {

                    tile.backgroundNode.color = [CCColor whiteColor];
                    tile.valueLabel.color = [CCColor blackColor];
                    [self spellWord:tile.value];
                    [_word setString:[_word lowercaseString]];
                    CCLOG(@"%@", _word);
                }
            }
        }
    }

    if (recognizer.state == UIGestureRecognizerStateChanged) {

    }

    if (recognizer.state == UIGestureRecognizerStateEnded) {
        for (Tile *tile in self.children) {
            if([tile isKindOfClass:[Tile class]]) {

                CGRect tileBoundingBox = tile.boundingBox;
                if (CGRectContainsPoint(tileBoundingBox, touchPoint)) {
                    tile.backgroundNode.color = [tile getColor:tile.value];
                    tile.valueLabel.color = [self getContrastColor:r green:g blue:b];

                }
            }
        }
    }
}

// allow for simultaneous gestures
- (BOOL)gestureRecognizer:(UIGestureRecognizer *) recognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
    return YES;
}

【问题讨论】:

    标签: ios objective-c swipe long-press


    【解决方案1】:

    回答您的问题:

    1. 这并没有让我觉得这是一种需要子类化UILongPressGestureRecognizer 的编码情况。话虽如此,子类化通常是清理视图控制器代码的好方法,因此您在视图控制器类中没有血腥的手势识别器代码。但是这里没有什么(据我所知)需要这样做。您通常会深入研究手势识别器的子类,其中需要一些特殊的自定义行为(例如,如果某些复杂的标准失败,则手势会失败)。不过,在我走这条路之前,我会先看看你是否可以通过标准手势实现所需的用户体验。

    2. 我可以看到滑动手势相互干扰的唯一原因是您已指定 shouldRecognizeSimultaneouslyWithGestureRecognizer 应返回 YES。这用于您需要同时运行多个识别器的情况,这在这里似乎没有必要(并且只是问题的根源)。

      我不清楚您是真的想要一个单独的滑动手势还是只想要一个手势(“长按并拖动”)。但是,如果您需要单独的滑动手势,您通常会通过指定 requireGestureRecognizerToFail 来指定手势识别器的相对优先级(例如,让滑动需要长按才能失败才能识别滑动)。但如果你真的只有一个手势(“长按并拖动”),那么只需要一个手势识别器。

    3. 似乎没有必要。如果您想在长按被识别后检测移动,您可以将“长按后移动”代码放在UIGestureRecognizedStateChangedUIGestureRecognizedStateChangedif 语句中,这发生在识别长按之后,但在用户抬起手指之前。 UILongPressGestureRecognizer 是一个连续的手势识别器,在最初识别出手势后,随着用户手指的移动,它将继续获得更新。


    我知道您没有要求提供代码,但如果您想要滑动手势以及长按手势(本质上是拿起并拖动它的想法),您可以执行以下操作.请注意,我使滑动手势要求长按失败,因此如果用户长按,则优先,否则它会滑动。但是您可能根本不需要滑动手势,所以如果您不需要它,只需将其完全移除即可:

    #import <UIKit/UIGestureRecognizerSubclass.h>
    
    - (void)viewDidLoad {
        [super viewDidLoad];
    
        UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongPress:)];
        [self.view addGestureRecognizer:longPress];
    
        // if you needed a second gesture, a swipe, completely distinct from the long press and drag
        // gesture, you could add it like so:
        //
        // UISwipeGestureRecognizer *swipe = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(handleSwipe:)];
        // [swipe requireGestureRecognizerToFail:longPress];
        // // do additional swipe configuration
        // [self.view addGestureRecognizer:swipe];
    }
    
    - (void)handleSwipe:(UISwipeGestureRecognizer *)gesture
    {
        // do your separate swipe stuff here
    }
    
    - (void)handleLongPress:(UILongPressGestureRecognizer *)gesture
    {
        static UIView *tileToMove;
        static CGPoint startCenter;
        static CGPoint startLocation;
    
        CGPoint location = [gesture locationInView:self.view];
    
        switch (gesture.state) {
            case UIGestureRecognizerStateBegan:
            {
                // find the tile
    
                tileToMove = [self findTileToMove:location];
                if (tileToMove) {
                    // if found, capture state ...
    
                    startCenter = tileToMove.center;
                    startLocation = location;
    
                    // ... and animate "pick up tile", so the user gets positive feedback
                    // that the drag/swipe portion of the gesture is starting.
    
                    [UIView animateWithDuration:0.25 animations:^{
                        tileToMove.transform = CGAffineTransformMakeScale(1.2, 1.2);
                    }];
                } else {
                    gesture.state = UIGestureRecognizerStateFailed;
                }
                break;
            }
            case UIGestureRecognizerStateChanged:
            {
                // move the tile as the user's finger moves
    
                CGPoint translate = CGPointMake(location.x - startLocation.x, location.y - startLocation.y);
    
                // note, if you want to constrain the translation to be, for example, on the
                // x-axis alone, you could do something like:
                //
                // CGPoint translate = CGPointMake(location.x - startLocation.x, 0);
    
                tileToMove.center = CGPointMake(startCenter.x + translate.x, startCenter.y + translate.y);
                break;
            }
            case UIGestureRecognizerStateEnded:
            {
                // animate "drop the tile"
    
                [UIView animateWithDuration:0.25 animations:^{
                    tileToMove.transform = CGAffineTransformIdentity;
    
                    // if you want the tile to "snap" to some location having let it go,
                    // set the `center` or `frame` here.
                }];
    
                // clear our variables, just in case
    
                tileToMove = nil;
                startCenter = CGPointZero;
                startLocation = CGPointZero;
                break;
            }
            default:
                break;
        }
    }
    
    - (UIView *)findTileToMove:(CGPoint)location
    {
        for (UIView *tile in self.tiles) {
            if (CGRectContainsPoint(tile.frame, location)) {
                return tile;
            }
        }
    
        return nil;
    }
    

    这可能与您正在寻找的 UI 不太完全相同,但它说明了:

    1. 如何有两个手势,其中一个需要另一个失败才能在手势之间建立优先级(如果你想要两个不同的手势,显然只有一个问题,你可能不这样做);

    2. 没有shouldRecognizeSimultaneouslyWithGestureRecognizer 方法,因为我不希望它们同时被识别。请注意,仅当您确实需要两个手势时才需要,您可能需要也可能不需要;和

    3. 如何让长按不仅能识别初始长按,还能识别随后的滑动/拖动动作。

    【讨论】:

    • 好的,这有点道理,但我该如何设置呢?长按后如何区分正常滑动和滑动?
    • @Dan - 我想知道你是否需要两个手势。你真的有两个手势(标准滑动和长按然后拖动)还是只有一个(长按和拖动手势)。如果您只需要后一种,那么显然解决方案会被简化(根本不需要滑动手势)。我已经相应地修改了我的答案。
    • 我确实需要这两种手势。滑动本身用于移动瓷砖。所有“图块”都位于由数组内部的数组组成的网格中。在没有长按的情况下滑动会移动瓷砖,所以这是绝对必要的。长按+拖动应该选择您长按的图块(我开始工作),然后当您将手指拖动到相邻的图块时,它会选择您拖动手指穿过的每个图块并将字母添加在一起以拼写一个单词,根据预加载的单词词典进行检查。长按根本不参与移动瓷砖。
    • @Dan 没关系。因此,您有两个手势,没有shouldRecognizeSimultaneouslyWithGestureRecognizer,只需按照您的概述调整长按处理程序。但是你会像我上面概述的那样做两个手势(显然取消注释滑动手势代码)。你应该去参加比赛了!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-03-29
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多