在滚动视图上拖动视图:touchchesBegan已收到,但没有touchesEnded或touchesCancelled

时间:2014-03-22 19:45:35

标签: ios iphone objective-c uiscrollview touchesbegan

作为iOS编程新手,我正在努力a word game for iPhone

应用结构为:scrollView -> contentView -> imageView -> image 1000 x 1000(此处fullscreen):

Xcode screenshot

我想我终于明白了如何在Xcode 5.1中使用启用了自动布局的UIScrollView

我只为contentView指定了足够的约束(尺寸1000 x 1000以及0),这定义了_scrollView.contentSize(我不必明确地设置它) - 之后,我的游戏板滚动和缩放就好了。

但是我在Tile.m中实现了可拖动的字母拼贴我遇到了麻烦。

我使用touchesBegan, touchesMoved, touchesEnded, touchesCancelled而非手势识别器(通常由StackOverflow用户建议),因为我在bigImage上显示带有阴影的较大字母图块图像(touchesBegan)。

我的拖动按以下方式实施:

  1. touchesBegan中,我从contentView移除了图块(并将其添加到主应用视图中)并显示带有阴影的bigImage
  2. touchesMoved移动磁贴
  3. touchesEndedtouchesCancelled我再次显示带有阴影的smallImage并 - 将图块添加到contentView或将其保留在主视图中(如果图块位于应用程序的底部)。
  4. 我的问题:

    大多数情况下这都有效,但有时(通常)我看到只调用touchesBegan,但其他touchesXXXX方法永远不会被调用:

    2014-03-22 20:20:20.244 ScrollContent[8075:60b] -[Tile touchesBegan:withEvent:]: Tile J 10 {367.15002, 350.98877} {57.599998, 57.599998}
    

    相反,scrollView由手指滚动,位于大瓷砖下方。

    这导致我的应用程序的屏幕上出现了许多带有阴影的大瓷砖,而滚动视图正在其下方拖动:

    problem screenshot

    如何解决这个问题?

    我确信通过查看热门的文字游戏,我的应用结构(自定义UIView被拖入/拖出UIScrollView)是可能的。

    我使用tile.exclusiveTouch = YESa custom hitTest method for the contentView - 但这没有帮助:

    - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
    {
        UIView* result = [super hitTest:point withEvent:event];
    
        return result == self ? nil : result;
    }
    

    更新1:

    我已尝试将以下代码添加到handleTileTouched

    _contentView.userInteractionEnabled = NO;
    _scrollView.userInteractionEnabled = NO;
    _scrollView.scrollEnabled = NO;
    

    然后将其设置回ViewController.mYES中的handleTileReleased - 但这没有用,而且看起来更像是对我的黑客攻击。

    更新2:

    可能在网上阅读了与UIScrollViewhitTest:withEvent:pointInside:withEvent:相关的所有内容(例如Hacking the responder chain和Matt Neuburg的Programming iOS在我看来,StackOverflow和Safari,解决方案是为我的应用程序的主视图实现hitTest:withEvent:方法:

    如果遇到Tile个对象,则应返回该对象。否则 - 应返回scrollView

    不幸的是,这不起作用 - 我可能遗漏了一些小事。

    我确信存在一个很好的解决方案 - 通过研究iOS的流行文字游戏。例如,在Zynga的Words with Friends ®应用程序中拖动和放置字母拼贴非常流畅,在下面的屏幕截图中,您可以看到它们可能使用UIScrollView(滚动条在角落中可见)和显示一个瓦片阴影(可能在touchesBegan方法中):

    app screenshot

    更新3:

    我已经创建了一个新项目来测试TomSwift建议的gesture recognizer,它显示了我对手势识别器的问题:磁贴大小变化太晚 - 当用户开始移动磁贴时会发生这种情况而不是在他接触它的那一刻:

    app screenshot

4 个答案:

答案 0 :(得分:7)

这里的问题是从视图层次结构中删除视图会使系统混乱,触摸会丢失。这是同一个问题(内部手势识别器使用相同的touchesBegan: API)。

https://github.com/LeoNatan/ios-newbie/commit/4cb13ea405d9f959f4d438d08638e1703d6c0c1e (我创建了拉取请求。)

我改变的是在触摸开始时不从内容视图中移除图块,而是仅在触摸结束时移动或取消。但这会产生一个问题 - 当拖动到底部时,图块隐藏在视图下方(由于滚动视图剪切到其边界)。所以我创建了一个克隆的tile,将它添加为视图控制器视图的子视图,并将其与原始tile一起移动。当触摸结束时,我删除克隆的瓷砖并将原件放在应该去的地方。

这是因为底栏不是scrollview层次结构的一部分。如果是,则不需要整个瓷砖克隆。

我还简化了瓷砖的定位。

答案 1 :(得分:2)

您可以在拖动图块时将滚动视图的userInteractionEnabled设置为NO,并在图块拖动结束时将其设置回YES

答案 2 :(得分:1)

你应该尝试使用手势识别器而不是原始的touchesBegan / touchesMoved。我之所以这么说,是因为UIScrollView使用的是手势识别器,默认情况下这些将会转移到正在运行的任何更高级别的手势识别器。

我把一个带有UIScrollView和嵌入式UIImageView的示例放在一起。与你的截图一样,在scrollView下面我有一些UIButton" Tiles&#34 ;,我将其子类化为TSTile对象。我这样做的唯一原因是暴露一些NSLayoutConstraints来访问/改变它们的高度/宽度(因为你使用自动布局与帧操作)。用户可以将瓷砖从其起始位置拖动到滚动视图中。

这似乎运作良好;一旦在tileview中重新设置了一个tile,我就没有能够拖动它。但这不应该太难。为此,您可以考虑在每个磁贴中放置一个长按手势识别器,然后当它触发时,您将关闭滚动视图中的滚动,这样顶级平移手势识别器就会启动。

或者,您可能能够继承UIScrollView并拦截UIScrollView的pan-gesture-recognition组委托回调,以阻止用户从磁贴开始时的平移。

@interface TSTile : UIButton
//$hook these up to width/height constraints in your storyboard!
@property (nonatomic, readonly) IBOutlet NSLayoutConstraint* widthConstraint;
@property (nonatomic, readonly) IBOutlet NSLayoutConstraint* heightConstraint;
@end

@implementation TSTile
@synthesize widthConstraint,heightConstraint;
@end

@interface TSViewController () <UIScrollViewDelegate, UIGestureRecognizerDelegate>
@end

@implementation TSViewController
{
    IBOutlet UIImageView*   _imageView;

    TSTile*                 _dragTile;
}

- (void)viewDidLoad
{
    [super viewDidLoad];

    UIPanGestureRecognizer* pgr = [[UIPanGestureRecognizer alloc] initWithTarget: self action: @selector( pan: )];
    pgr.delegate = self;

    [self.view addGestureRecognizer: pgr];
}

- (UIView*) viewForZoomingInScrollView:(UIScrollView *)scrollView
{
    return _imageView;
}

- (BOOL) gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
    CGPoint pt = [gestureRecognizer locationInView: self.view];

    UIView* v = [self.view hitTest: pt withEvent: nil];

    return [v isKindOfClass: [TSTile class]];
}

- (void) pan: (UIGestureRecognizer*) gestureRecognizer
{
    CGPoint pt = [gestureRecognizer locationInView: self.view];

    switch ( gestureRecognizer.state )
    {
        case UIGestureRecognizerStateBegan:
        {
            NSLog( @"pan start!" );

            _dragTile = (TSTile*)[self.view hitTest: pt withEvent: nil];

            [UIView transitionWithView: self.view
                              duration: 0.4
                               options: UIViewAnimationOptionAllowAnimatedContent
                            animations:^{

                                _dragTile.widthConstraint.constant = 70;
                                _dragTile.heightConstraint.constant = 70;
                                [self.view layoutIfNeeded];
                            }
                            completion: nil];
        }
            break;

        case UIGestureRecognizerStateChanged:
        {
            NSLog( @"pan!" );

            _dragTile.center = pt;
        }
            break;

        case UIGestureRecognizerStateEnded:
        {
            NSLog( @"pan ended!" );

            pt = [gestureRecognizer locationInView: _imageView];

            // reparent:
            [_dragTile removeFromSuperview];
            [_imageView addSubview: _dragTile];

            // animate:
            [UIView transitionWithView: self.view
                              duration: 0.25
                               options: UIViewAnimationOptionAllowAnimatedContent
                            animations:^{

                                _dragTile.widthConstraint.constant = 40;
                                _dragTile.heightConstraint.constant = 40;
                                _dragTile.center = pt;
                                [self.view layoutIfNeeded];
                            }
                            completion:^(BOOL finished) {

                                _dragTile = nil;
                            }];
        }
            break;

        default:
            NSLog( @"pan other!" );
            break;
    }
}

@end

答案 3 :(得分:1)

我还认为你应该使用UIGestureRecognizer,更准确地说,每个瓷砖上的UILongPressGestureRecognizer一旦被识别就会处理平底锅。

对于细粒度控制,您仍然可以使用识别器&#39; delegate