【问题标题】:Forward touches to a UIScrollView向前触摸到 UIScrollView
【发布时间】:2014-02-28 12:44:36
【问题描述】:

我有两个视图控制器。视图控制器 A 有一个 UIScrollView 并呈现视图控制器 B。演示是交互式的,由 scrollView.contentOffset 控制。

我想集成一个交互式关闭过渡:向上平移时,应该以交互方式关闭 ViewController B。交互式关闭过渡还应该控制 ViewController A scrollView。

我的第一次尝试是使用UIPanGestureRecognizer 并根据平移距离设置scrollView.contentOffset。这可行,但是当平移手势结束时,滚动视图偏移必须动画到结束位置。使用-[UIScrollView setContentOffset:animated: 不是一个好的解决方案,因为它使用线性动画,没有考虑当前的平移速度,也不能很好地减速。

所以我认为应该可以将我的平移手势识别器中的触摸事件输入到滚动视图中。这应该给我们所有漂亮的滚动视图动画行为。

我尝试在我的UIPanGestureRecognizer 子类中覆盖-touchesBegan/Moved/Ended/Cancelled withEvent: 方法,如下所示:

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    [scrollView touchesBegan:touches withEvent:event];
    [scrollView.panGestureRecognizer touchesBegan:touches withEvent:event];

    [super touchesBegan:touches withEvent:event];
}

但显然有些东西阻止了滚动视图进入tracking 模式。 (它确实去了dragging = YES,但仅此而已。)我验证了滚动视图是userInteractionEnabled,没有隐藏并添加到视图层次结构中。

那么如何将我的触摸事件转发到UIScrollView

【问题讨论】:

  • 一个解决方法建议,也许您可​​以手动更新contentOffset,而无需从您的平移手势中获得动画?
  • 这就是我在过渡的交互部分所做的。但是,当我轻弹 VC B 视图时,我必须以非交互方式继续动画。
  • 如果我理解得很好,你在 A 的滚动视图中有一个 viewController B 吗?你的 viewController B 是由 UIPanGesture 操纵的吗?所以如果你上去,它会让viewController B消失,同时滚动A?
  • @AncAinu,不,两个视图控制器都是独立的,VC B 以模态方式呈现在 A 上。平移时,VC B 以交互方式关闭。当平移手势识别器触发时,dismissViewControllerAnimated:completion: 被调用,-startInteractiveTransition: 将 VC A 滚动视图添加回容器。从这一点开始,我想将触摸事件转发到滚动视图。

标签: ios iphone cocoa-touch uiscrollview uigesturerecognizer


【解决方案1】:

在阅读了描述UIScrollView 事件流的interesting answer 之后,我得出的结论是,尝试从手势识别器“远程控制”滚动视图可能很难实现,因为触摸在路由时会发生变化到视图和手势识别器。由于UITouch 不符合NSCopying,我们也无法克隆触摸事件以便稍后在未修改状态下发送它们。

虽然没有真正解决我所要求的问题,但我找到了一种解决方法来完成我所需要的。我刚刚向视图控制器 B 添加了一个滚动视图,并将其与 VC A 的滚动视图同步(垂直滚动时添加到视图层次结构中):

// delegate of VC B's scrollView
- (void)scrollViewDidScroll:(UIScrollView*)scrollView
    scrollViewA.contentOffset = scrollView.contentOffset;
}

感谢 came up with the idea 的 Friedrich Markgraf。

【讨论】:

  • 尽管没有真正解决问题,但只是解决了这个问题,但仍将勾选标记为我自己的答案。很高兴将勾选切换到任何实际有效的答案!
【解决方案2】:

尝试继承 UIScrollView 并覆盖 hitTest:withEvent: 以便 UIScrollView 在其边界之外拾取触摸。然后你就可以免费获得所有漂亮的UIScrollView 动画。像这样的:

@interface MagicScrollView : UIScrollView
@end

@implementation MagicScrollView

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    // Intercept touches 100pt outside this view's bounds on all sides. Customize this for the touch area you want to intercept
    if (CGRectContainsPoint(CGRectInset(self.bounds, -100, -100), point)) {
        return self;
    }
    return nil;
}

@end

或者在 Swift 中(感谢 Edwin Vermeer!)

class MagicScrollView: UIScrollView {
    override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? {
        if CGRectContainsPoint(CGRectInset(self.bounds, -100, -100), point) {
            return self
        }
        return nil
    } 
}

您可能还需要覆盖UIScrollView 的超级视图上的pointInside:withEvent:,具体取决于您的布局。

有关更多信息,请参阅以下问题:interaction beyond bounds of uiview

【讨论】:

  • 对于 Swift 你可以使用:class MagicScrollView: UIScrollView { override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? { if CGRectContainsPoint(CGRectInset(self.bounds, -100, -100), point) { return self } return nil } }
  • 感谢@EdwinVermeer!在答案中添加了 swift 版本。
【解决方案3】:

作为@johnboiles 解决方案的补充。当您捕获整个矩形时,将跳过滚动视图内的元素的触摸。您可以添加额外的触摸区域,对于普通的滚动视图,只需将其传递给 super.hitTest,如下所示:

class MagicScrollView: UIScrollView {
    override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? {
        if CGRectContainsPoint(CGRect(x: self.bounds.origin.x - 30, y: self.bounds.origin.y, width: 30, height: self.bounds.size.height), point) {
            return self
        }
        if CGRectContainsPoint(CGRect(x: self.bounds.origin.x + self.bounds.size.width, y: self.bounds.origin.y, width: 30, height: self.bounds.size.height), point) {
            return self
        }
        return super.hitTest(point, withEvent: event)
    }
}

【讨论】:

    【解决方案4】:

    我在超级视图中有一个滚动视图,具有左右边框。为了使这些边框可滚动,这是我的方法:

    class CustomScrollView: UIScrollView {
      override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? {
        if let view = super.hitTest(point, withEvent: event) {
          return view
        }
    
        guard let superview = superview else {
          return nil
        }
    
        return superview.hitTest(superview.convertPoint(point, fromView: self), withEvent: event)
      }
    
      override func pointInside(point: CGPoint, withEvent event: UIEvent?) -> Bool {
        if super.pointInside(point, withEvent: event) {
          return true
        }
    
        guard let superview = superview else {
          return false
        }
    
        return superview.pointInside(superview.convertPoint(point, fromView: self), withEvent: event)
      }
    }
    

    完美运行

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-09-01
      • 2023-03-06
      • 2012-01-31
      • 2012-08-10
      • 1970-01-01
      • 2023-04-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多