问题 转发到UIScrollView


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

我想整合一个交互式消除过渡:当平移时,ViewController B应该以交互方式被解雇。交互式dismiss转换还应控制ViewController的scrollView。

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

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

我试过压倒了 -touchesBegan/Moved/Ended/Cancelled withEvent: 在我的方法 UIPanGestureRecognizer 像这样的子类:

- (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 但这就是它。)我验证了scrollView是 userInteractionEnabled,不隐藏并添加到视图层次结构中。

那么我怎样才能将我的触摸事件转发给 UIScrollView


4236
2018-02-04 13:49


起源

一个变通方法的建议,也许你可以更新 contentOffset 手动平移手势没有动画? - coverback
这就是我在过渡的互动部分所做的事情。但是,当我轻弹VC B视图时,我将以非交互方式继续动画。 - Ortwin Gentz
如果我理解得很好,你在A的滚动视图中有一个viewController B?你的viewController B是由UIPanGesture操纵的吗?所以,如果你上去,它会使viewController B消失并同时滚动A? - AncAinu
@AncAinu,不,两个视图控制器都是独立的,VC B在A上以模态方式呈现。当平移时,VC B以交互方式被解除。当平移手势识别器触发时, dismissViewControllerAnimated:completion: 被称为调用 -startInteractiveTransition: 它将VC A滚动视图添加回容器。从这一点来说,我想将触摸事件转发到滚动视图。 - Ortwin Gentz


答案:


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

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

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

感谢Friedrich Markgraf 提出了这个想法


11
2018-02-05 22:19



尽管没有真正解决问题但仅解决问题,但是给我自己的答案打勾。很高兴把滴答切换到任何实际工作的答案! - Ortwin Gentz


尝试继承 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
    } 
}

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

有关详细信息,请参阅以下问题: 超越uiview界限的互动


3
2017-12-08 06:12



对于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}} - Edwin Vermeer
谢谢@EdwinVermeer!在答案中添加了swift版本。 - johnboiles


也许你可以尝试在UIPanGesture委托中使用该方法:

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer;

我不知道它会如何反应,因为你的观点是模态的。但也许它可以成功,如果没有直接使用beginTouch等触摸,这是一个痛苦的屁股。

告诉我它是否有帮助?


0
2018-02-04 17:05



我不赞成被投票,如果答案不是好的,请说出来。然后我可以提出另一个。 - AncAinu
你是对的,应该在我投票时写下评论。对不起。原因是:您引用的方法是允许同一视图上的两个手势识别器同时开始识别,如捏和平移。您没有解释此方法在此上下文中的相关性或如何用于解决OP问题。我觉得你不懂OPs问题(哪个 是 复杂,公平)而你只是在猜测。 - DarkDust
谢谢。没有试过这个,但我怀疑它是否会起作用我在答案中描述的原因。至少我发现了一个解决方法:) - Ortwin Gentz
Upvoted以补偿downvote。至少这是一个值得尝试的想法! - Ortwin Gentz
@DarkDust就是这个问题,因为问题很复杂,如果你开始向所有有想法的人投票(我认为这不是“太”愚蠢)那么没有人会回答......一个好的答案可以来自“思考“ 关于它。但是,如果有人得到了好的答案,他可以直接帮助,但是等待这一点,最好是集思广益,而不仅仅是责怪那些试图提供帮助的人。 - AncAinu


作为@johnboiles解决方案的补充。当您捕获整个rect时,将跳过对scrollview内部元素的触摸。您可以添加aditional触摸区域,对于普通的scrollview,只需将其传递给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)
    }
}

0
2018-06-23 13:42





我在superview里面有一个滚动视图,有左右边框。为了使这些边框可滚动,这是我的方法:

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)
  }
}

完美的工作


0
2017-10-11 09:53