在滚动视图上拖动视图:已收到 touchesBegan,但没有 touchesEnded 或 touchesCancelled
Posted
技术标签:
【中文标题】在滚动视图上拖动视图:已收到 touchesBegan,但没有 touchesEnded 或 touchesCancelled【英文标题】:Dragging views on a scroll view: touchesBegan is received, but no touchesEnded or touchesCancelled 【发布时间】:2014-03-22 19:45:34 【问题描述】:作为一名 ios 编程新手,我正在为a word game for iPhone 苦苦挣扎。
应用结构为:scrollView -> contentView -> imageView -> image 1000 x 1000
(此处为fullscreen):
我想我终于明白了如何在 Xcode 5.1 中使用启用自动布局的UIScrollView
:
我只是为 contentView
指定了足够的约束(尺寸 1000 x 1000 和 0 到父级),这定义了 _scrollView.contentSize
(我不必显式设置) - 之后我的游戏板滚动和缩放就好了。
但是我在Tile.m 中实现的可拖动字母图块遇到了问题。
我使用touchesBegan, touchesMoved, touchesEnded, touchesCancelled
而不是手势识别器(*** 用户经常建议),因为我在touchesBegan
上显示带有阴影的较大字母平铺图像(bigImage
)。
我的拖拽是这样实现的:
-
在
touchesBegan
中,我从contentView
中删除磁贴(并将其添加到主应用程序视图)并显示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
是用手指滚动的,位于大图块下方。
这会导致我的应用屏幕上出现许多带有阴影的大图块,而滚动视图被拖到它们下方:
请问如何解决?
通过查看流行的文字游戏,我确定我的应用程序结构(自定义 UIView
s 拖入/拖出 UIScrollView
)是可能的。
我使用 tile.exclusiveTouch = YES
和 a 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.m 的handleTileReleased
中将其设置回YES
- 但这无济于事,而且对我来说更像是一种黑客攻击。
更新 2:
可能已经阅读了与 UIScrollView
、hitTest:withEvent:
和 pointInside:withEvent:
相关的所有内容 - 在网络上(例如 Hacking the responder chain 和 Matt Neuburg 的 Programming iOS 书)、*** 和 Safari,在我看来,一个解决方案是为我的应用程序的主视图实现hitTest:withEvent:
方法:
如果Tile
对象被命中,它应该被返回。否则 - 应返回 scrollView
。
很遗憾,这不起作用 - 我可能遗漏了一些小问题。
而且我确信存在一个很好的解决方案 - 通过研究适用于 iOS 的流行文字游戏。例如,在 Zynga 的 Words with Friends ® 应用程序中,字母拼贴的拖动和放置非常流畅,在下面的屏幕截图中,您可以看到它们可能使用 UIScrollView
(滚动条在角落可见)并显示拼贴阴影(可能在 @ 987654369@方法):
更新 3:
我创建了一个新项目来测试 TomSwift 建议的 gesture recognizer,它显示了我在使用手势识别器时遇到的问题:磁贴大小更改得太晚 - 当用户开始移动磁贴而不是在他触摸它的那一刻:
【问题讨论】:
啊,法伯先生和永无止境的文字游戏问题。 :D:-)
我的(移动和桌面)纸牌游戏处于相同阶段,现在(从 4 岁开始)它可以正常工作:facebook.com/appcenter/video-preferans 我只需要通过大量愚蠢的问题,然后那我就好了。请耐心等待我的 iOS 新手。
我想我知道问题出在哪里。如果我注释掉通知帖子,它工作“正常”,没有触摸进入滚动视图
【参考方案1】:
这里的问题是从视图层次结构中删除视图会使系统感到困惑,失去了触摸。这是同一个问题(内部手势识别器使用相同的touchesBegan:
API)。
https://github.com/LeoNatan/ios-newbie/commit/4cb13ea405d9f959f4d438d08638e1703d6c0c1e (我创建了一个拉取请求。)
我所做的更改是在触摸开始时不从内容视图中删除磁贴,而仅在触摸结束或取消时移动。但这会产生一个问题 - 当拖到底部时,图块隐藏在视图下方(由于滚动视图剪切到其边界)。所以我创建了一个克隆图块,将其添加为视图控制器视图的子视图,并将其与原始图块一起移动。当触摸结束时,我移除克隆的图块并将原图放在它应该去的地方。
这是因为底部栏不是滚动视图层次结构的一部分。如果是,则不需要整个磁贴克隆。
我还大大简化了图块的定位。
【讨论】:
@AlexanderFarber 我不确定。根据我的经验,alpha 0 仍然会导致事情正常运行,而hidden
会导致他们跳过某些东西——这就是我选择 alpha 的原因。
@AlexanderFarber 缩小并将磁贴拖到内容视图之外。这可以确保如果 tile 落在内容视图之外,它会返回到堆栈中。
@AlexanderFarber 我想在您开始对 iOS 感到更舒服并开始制作真正的应用程序时成为一名 beta 测试员。 :-)
太棒了,我计划 iOS + android + Facebook(用于桌面,在 Flex + jQuery 中)应用程序...通过 Urban Airship + 套接字通知...您认为移动从Tile.m
到ViewController.m
的touchesXXX
方法?
是的,是的。控制器应该处理视图逻辑。【参考方案2】:
您可以在拖动磁贴时将滚动视图的userInteractionEnabled
设置为NO
,并在磁贴拖动结束时将其设置回YES
。
【讨论】:
我尝试在handleTileTouched
中将_scrollView.userInteractionEnabled
设置为NO
并在handleTileReleased
中设置YES
- 它没有帮助(所以我再次评论它:@987654321 @ ) 另外,我怀疑这是一种解决方法...必须有更好的解决方案(例如仅将触摸事件传递给拖动的字母图块)。
您可以尝试使用 UIPanGestureRecognizer 代替。您可以使用this 快速实现
使用 UIPanGestureRecognizer
我无法显示带有阴影的 bigImage
(当前显示在 touchesBegan
- 请参阅我的原始问题)。
您可以使用 TapGesture 添加阴影,并使用 PanGesture 移动磁贴。这样你会立即看到阴影。
应该,你需要将你的视图控制器设置为委托并实现this。在 gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:
中返回 YES 将使您的两个手势都被识别。【参考方案3】:
您真的应该尝试使用手势识别器而不是原始的 touchesBegan/touchesMoved。我这样说是因为 UIScrollView 正在使用手势识别器,默认情况下这些将让给正在运行的任何更高级别的手势识别器。
我整理了一个包含 UIScrollView 和嵌入式 UIImageView 的示例。与您的屏幕截图一样,在 scrollView 下方我有一些 UIButton“Tiles”,我将其子类化为 TSTile 对象。我这样做的唯一原因是公开一些 NSLayoutConstraints 以访问/更改它们的高度/宽度(因为您使用的是自动布局与框架操作)。用户可以将磁贴从它们的起始位置拖到滚动视图中。
这似乎运作良好;一旦它在滚动视图中重新设置为父级,我就没有连接拖动磁贴的能力。但这不应该太难。为此,您可能会考虑在每个图块中放置一个长按手势识别器,然后当它触发时,您将关闭滚动视图中的滚动,这样***平移手势识别器就会启动。
或者,您也许可以继承 UIScrollView 并拦截 UIScrollView 的 pan-gesture-recognizer 委托回调,以在用户从磁贴开始时阻止平移。
@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
【讨论】:
【参考方案4】:我还认为您应该在每个一旦识别将处理平移的图块上使用UIGestureRecognizer
,更准确地说是UILongPressGestureRecognizer
。
对于细粒度控制,您仍然可以使用识别器的delegate
。
【讨论】:
以上是关于在滚动视图上拖动视图:已收到 touchesBegan,但没有 touchesEnded 或 touchesCancelled的主要内容,如果未能解决你的问题,请参考以下文章