作为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
而非手势识别器(通常由StackOverflow用户建议),因为我在bigImage
上显示带有阴影的较大字母图块图像(touchesBegan
)。
我的拖动按以下方式实施:
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
被拖入/拖出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的YES
中的handleTileReleased
- 但这没有用,而且看起来更像是对我的黑客攻击。
更新2:
可能在网上阅读了与UIScrollView
,hitTest:withEvent:
和pointInside:withEvent:
相关的所有内容(例如Hacking the responder chain和Matt Neuburg的Programming iOS在我看来,StackOverflow和Safari,解决方案是为我的应用程序的主视图实现hitTest:withEvent:
方法:
如果遇到Tile
个对象,则应返回该对象。否则 - 应返回scrollView
。
不幸的是,这不起作用 - 我可能遗漏了一些小事。
我确信存在一个很好的解决方案 - 通过研究iOS的流行文字游戏。例如,在Zynga的Words with Friends ®应用程序中拖动和放置字母拼贴非常流畅,在下面的屏幕截图中,您可以看到它们可能使用UIScrollView
(滚动条在角落中可见)和显示一个瓦片阴影(可能在touchesBegan
方法中):
更新3:
我已经创建了一个新项目来测试TomSwift建议的gesture recognizer,它显示了我对手势识别器的问题:磁贴大小变化太晚 - 当用户开始移动磁贴时会发生这种情况而不是在他接触它的那一刻:
答案 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
。