在 UIScrollView 中拖动一个 UIView

Posted

技术标签:

【中文标题】在 UIScrollView 中拖动一个 UIView【英文标题】:Dragging an UIView inside UIScrollView 【发布时间】:2009-09-26 18:23:01 【问题描述】:

我正在尝试解决 iPhone 上拖放的基本问题。这是我的设置:

我有一个 UIScrollView,它有一个大的内容子视图(我可以滚动和缩放它) 内容子视图有几个小图块作为应在其中拖动的子视图。

我的 UIScrollView 子类有这个方法:

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event 
    UIView *tile = [contentView pointInsideTiles:[self convertPoint:point toView:contentView] withEvent:event];
    if (tile) 
        return tile;
     else 
        return [super hitTest:point withEvent:event];
    

内容子视图有这个方法:

- (UIView *)pointInsideTiles:(CGPoint)point withEvent:(UIEvent *)event 
    for (TileView *tile in tiles) 
        if ([tile pointInside:[self convertPoint:point toView:tile] withEvent:event])
            return tile;
    

    return nil;

平铺视图有这个方法:

- (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event 
    UITouch *touch = [touches anyObject];   
    CGPoint location = [touch locationInView:self.superview];

    self.center = location;

这可行,但并不完全正确:在拖动过程中,瓷砖有时会“掉落”。更准确地说,它停止接收 touchesMoved: 调用,并且滚动视图开始滚动。我注意到这取决于拖动速度:拖动速度越快,图块“下落”的速度就越快。

关于如何让瓷砖粘在拖动的手指上的任何想法?

【问题讨论】:

你有这方面的样品吗? 【参考方案1】:

我一直在努力解决同样的问题 - 我试图在软木板上创建一个带有很多“卡片”(UIView 子类)的界面,并让软木板区域可滚动,但仍然能够拖放 -放下卡片。我正在做上面的 hitTest() 解决方案,但一位 Apple 工程师问我为什么要这样做。他们建议的更简单的解决方案如下:

1) 在 UIScrollView 类中,将 canCancelContentTouches 的值设置为 NO - 这告诉 UIScrollView 类允许在子视图中进行触摸(或者,在这种情况下,在子视图的子视图中)。

2) 在我的“卡片”类中,将 ExclusiveTouch 设置为 YES - 这告诉子视图它拥有其中的触摸。

在此之后,我能够在卡片周围拖动并仍然滚动子视图。它比上面的 hitTest() 解决方案更简单、更干净。

(顺便说一句,如果您使用的是 ios 3.2 或 4.0 或更高版本,请使用 UIPanGestureRecognizer 类来处理拖放逻辑 - 拖放动作比覆盖 touchesBegan()/touchesMoved( )/touchesEnded().)

【讨论】:

1 正是我想要的。 canCancelContentTouches 对于这样的变量来说似乎是一个相当糟糕的名称。 这太棒了!有用!但我希望我能理解为什么......你能详细说明一下吗? 它的工作原理如下:(1)我们允​​许子视图获取触摸事件(“canCancelContentTouches = NO”操作)(2)卡片视图抓取其中的触摸(exclusiveTouch = YES )。请记住,iOS 中显示的对象大部分是一系列 UIView 派生类,因此我们要做的是确保将触摸传播到适当的视图。一旦你得到了接触,作为开发人员的你来决定你想用它们做什么。 也许这是一个孤立的案例,但我确实注意到这在模拟器中并不是特别好(通常在拖动子视图之前滚动接管),但在真实设备上它可以工作完美。谢谢。【参考方案2】:

已解决:事实证明,tile 中还应该有 touchesBegan: 和 touchesEnded: 实现(在我的情况下,有空方法有帮助),否则手势开始传播到父视图,并且它们以某种方式拦截了手势。对拖动速度的依赖是虚构的。

【讨论】:

【参考方案3】:

首先,设置:

scrollview.canCancelContentTouches = NO;

yourSubView. exclusiveTouch = YES;

然后在你的子视图手势处理函数中,

- (void)handleSubviewMove:(UIPanGestureRecognizer *)gesture 

if(gesture.state == UIGestureRecognizerStateBegan) 
    if(_parentScrollView != nil) 
        _parentScrollView.scrollEnabled = NO;
    

if(gesture.state == UIGestureRecognizerStateEnded) 
    if(_parentScrollView != nil) 
        _parentScrollView.scrollEnabled = YES;
    


CGPoint translation = [gesture translationInView:[self superview]];
/* handle your view's movement here. */
[gesture setTranslation:CGPointZero inView:[self superview]];

【讨论】:

【参考方案4】:

根据您共享的代码,您的 touchesMoved: 方法似乎只会为磁贴内的手势调用。在每次触摸时,您将磁贴移动到该触摸的中心,因此在手势退出磁贴之前,缓慢的移动都会在磁贴内产生更新——并且磁贴将“赶上”指尖。但是,当手势更快时,(x,y) touchesMoved 事件将相距更远,当一个 (x,y) 点距离最后一个点足够远以至于它在已经平铺了。

您可以通过在一个足够大以覆盖整个可拖动区域的超级视图中捕获移动,并在该超级视图中控制磁贴的移动来解决此问题。

顺便问一下,您是否有理由重写 hitTest: 方法?使用内置实现可能更容易(并且可能更有效?)。

【讨论】:

没有 hitTest:这根本不起作用:拖动磁贴会导致滚动整个内容子视图。然而,我能够解决这个问题:事实证明,磁贴中还应该有 touchesBegan: 和 touchesEnded: 实现(在我的情况下有空方法有帮助),否则手势开始传播到父视图,并且他们正在拦截它以某种方式。

以上是关于在 UIScrollView 中拖动一个 UIView的主要内容,如果未能解决你的问题,请参考以下文章

在无限 UIScrollView 中跨页面拖动视图

如何在 UIScrollView 中同时拖动和移动某些东西并滚动

拖动包含 UIScrollView 的 UIView

在 UIScrollView 中模拟拖动结束

UIScrollview 仅在拖动 uiviews 时滚动

UIScrollView 通过在框架内拖动来滚动