在滚动视图上拖动视图:touchchesBegan已收到,但没有touchesEnded或touchesCancelled
作为iOS编程新手,我正在为iPhone的文字游戏而苦苦挣扎。
应用程序结构是: scrollView -> contentView -> imageView -> image 1000 x 1000
(这里是全屏 ):
我想我终于明白了如何使用在Xcode 5.1中启用自动布局的UIScrollView
:
我只是为contentView
指定了足够的约束(尺寸1000 x 1000以及0到父级),这定义了_scrollView.contentSize
(我没有明确地设置它) – 之后我的游戏板滚动和缩放就好了。
但是,我在Tile.m中实现了可拖动的字母拼图我遇到了麻烦 。
我使用touchesBegan, touchesMoved, touchesEnded, touchesCancelled
而不是手势识别器(通常由StackOverflow用户建议),因为我在touchesBegan
上显示带阴影( bigImage
)的较大字母图块图像。
我的拖动按以下方式实现:
- 在
touchesBegan
我从contentView
删除了tile(并将其添加到主app视图中)并显示带阴影的bigImage
。 - 在
touchesMoved
我移动瓷砖 - 在
touchesEnded
或touchesCancelled
我再次显示带阴影的smallImage
– 将图块添加到contentView
或将其保留在主视图中(如果图块位于应用程序的底部)。
我的问题:
大多数情况下这是有效的,但有时(通常)我看到只调用了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
在大瓷砖下方由手指滚动。
这会导致许多大瓷砖的阴影位于我的应用程序的屏幕上,而滚动视图正在其下方拖动:
如何解决这个问题?
我确信我的应用程序结构(可以通过UIScrollView
拖入/移出自定义UIView
) – 通过查看流行的文字游戏。
我使用tile.exclusiveTouch = YES
和contentView的自定义hitTest方法 – 但这没有帮助:
- (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.m的 handleTileReleased
中将其设置回YES
– 但这没有帮助,看起来更像是对我的黑客攻击。
更新2:
阅读可能与UIScrollView
相关的所有东西, hitTest:withEvent:
和pointInside:withEvent:
– 在网上(例如, 黑客攻击链和Matt Neuburg的编程iOS书),StackOverflow和Safari,在我看来,解决方案是是为我的应用程序的主视图实现hitTest:withEvent:
方法:
如果击中了Tile
对象,则应返回该对象。 否则 – 应返回scrollView
。
不幸的是,这不起作用 – 我可能遗漏了一些小问题。
我确信存在一个很好的解决方案 – 通过研究iOS的流行文字游戏。 例如,在Zynga的Words withFriends®应用程序中拖动和放置字母拼贴非常流畅,在下面的屏幕截图中,您可以看到它们可能使用UIScrollView
(滚动条在角落中可见)并显示拼贴阴影(可能在touchesBegan
方法中) ):
更新3:
我已经创建了一个新项目来测试TomSwift建议的手势识别器,它显示了我对手势识别器的问题:磁贴大小变化太晚 – 当用户开始移动磁贴时发生,而不是在他触摸它时:
这里的问题是从视图层次结构中删除视图会使系统混乱,触摸会丢失。 这是同一个问题(内部手势识别器使用相同的touchesBegan:
API)。
https://github.com/LeoNatan/ios-newbie/commit/4cb13ea405d9f959f4d438d08638e1703d6c0c1e (我创建了一个拉取请求。)
我改变的是在触摸开始时不从内容视图中移除图块,而是仅在触摸结束时移动或取消。 但这会产生一个问题 – 当拖动到底部时,图块隐藏在视图下方(由于滚动视图剪切到其边界)。 所以我创建了一个克隆的tile,将其添加为视图控制器视图的子视图,并将其与原始tile一起移动。 当触摸结束时,我删除克隆的瓷砖并将原件放在应该去的地方。
这是因为底栏不是scrollview层次结构的一部分。 如果是,则不需要整个瓷砖克隆。
我也简化了瓷砖的定位。
您可以在拖动切片时将scrollview的userInteractionEnabled
设置为NO
,并在切片拖动结束时将其设置回YES
。
您应该尝试使用手势识别器而不是原始的touchesBegan / touchesMoved。 我之所以这么说是因为UIScrollView正在使用手势识别器,默认情况下这些将放弃到正在运行的任何更高级别的手势识别器。
我把一个带有UIScrollView和嵌入式UIImageView的示例放在一起。 与你的截图一样,在scrollView下面我有一些UIButton“Tiles”,我将其子类化为TSTile对象。 我这样做的唯一原因是暴露一些NSLayoutConstraints来访问/改变它们的高度/宽度(因为你使用的是自动布局与帧操作)。 用户可以将瓷砖从其起始位置拖动到滚动视图中。
这似乎运作良好; 一旦在scrollview中重新设置了父级,我就没有能够拖动它。 但这不应该太难。 为此,您可以考虑在每个磁贴中放置一个长按手势识别器,然后当它触发时,您将关闭滚动视图中的滚动,这样顶级平移手势识别器就会启动。
或者,您可能能够子类化UIScrollView并拦截UIScrollView的pan-gesture-recognitionizer委托回调以阻止用户从磁贴启动时的平移。
@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 () @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
我还认为你应该使用UIGestureRecognizer
,更准确地说,每个瓷砖上的UILongPressGestureRecognizer
一旦被识别就会处理pan。
对于细粒度控制,您仍然可以使用识别器的delegate
。