水平 UIScrollView 内部有垂直 UIScrollViews - 滚动外部水平视图时如何防止滚动内部滚动视图?

Posted

技术标签:

【中文标题】水平 UIScrollView 内部有垂直 UIScrollViews - 滚动外部水平视图时如何防止滚动内部滚动视图?【英文标题】:Horizontal UIScrollView having vertical UIScrollViews inside - how to prevent scrolling of inner scroll views when scrolling outer horizontal view? 【发布时间】:2012-11-07 15:31:18 【问题描述】:

找不到解决办法。

我正在构建一个具有大滚动视图的应用程序,它具有分页(水平)。 在这个滚动视图内部,有一个 UIView 的网格,每个内部都有一个 UIScrollview,带有垂直滚动视图。

现在,重点是,当我分页我的“大”滚动视图时,有时触摸会卡在网格 UIView 内的一个小滚动视图中。

我不知道如何避免它 - 尝试使用 hitTest 技巧但仍然找不到答案。

希望我很清楚......

感谢您的帮助。

编辑:

这是更大的滚动视图:

@implementation UIGridScrollView
- (id)initWithFrame:(CGRect)frame

    self = [super initWithFrame:frame];
    self.pagingEnabled;
    return self;

@end

现在,我在这个 UIGridScroll 视图中添加了这个视图作为子视图:

@implementation UINoteView

IBOutlet UIScrollView *_innerScrollView; // this scrollview frame is in the size of the all UINoteView


- (void)awakeFromNib

    _innerScrollView.contentSize = CGSizeMake(_innerScrollView.frame.size.width, _innerScrollView.frame.size.height+50.0f);

@end

分页效果很好,内部滚动视图效果很好,但是当我分页较大的笔记视图时,我的手指在 _innerScrollView 中“卡住”了太多次。

谢谢!

【问题讨论】:

您能否发布一些代码来展示您如何设置滚动视图?如果没有用户可以触摸的任何地方并且正在触摸左右(父)滚动视图,则用户的触摸将始终在较小的上下滚动视图中注册。 谢谢,尝试添加一些东西.. 我有一个类似的应用程序。我认为系统决定是滚动内部滚动视图还是外部滚动视图有一个余量。但是,我认为它不会解决您的问题,但您可以尝试避免使用中间 UIView (UINoteView) 并将您的 innerScrollView 直接添加到外部。这就是我在我的案例中所做的,用户体验一点也不差。 我想通过悬赏这个问题来加入@AviTsadok 问题。 【参考方案1】:

@stanislaw,我刚刚在 iPhone 设备上尝试了您建议的解决方案。

我明白你的问题了。

您的代码确实可以防止偶尔滚动垂直视图,但我认为这不是同时进行手势识别的工作 - 注释您为内部视图提供的整个代码并使用外部滚动视图的代码并进行以下修改:

@interface OuterHorizontalScrollView : UIScrollView ...
@property (weak) InnerVerticalScrollView *currentActiveView; // Current inner vertical scroll view displayed.
@end

@implementation OuterHorizontalScrollView
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer 
    if (self.currentActiveView.dragging == NO) 
        self.currentActiveView.scrollEnabled = NO; // The presence of this line does the job
    
    return YES;


- (void)scrollViewDidEndDragging:(PlacesScrollView *)scrollView willDecelerate:(BOOL)decelerate 
    self.currentActiveView.scrollEnabled = YES; // This is very likely should be done for all subviews, not only a current.
    
@end

【讨论】:

它似乎真的可以在没有任何额外代码的情况下为内部滚动视图工作,但我需要更多地检查它。谢谢你的回答! @AviTsadok,我想知道,这个解决方案是否源自我的解决方案并进一步简化,是否能解决您的原始问题以及解决我的问题。【参考方案2】:

您可以继承 UIScrollView 并实现 gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:,允许滚动视图的内置 panGestureRecognizer 与另一个滚动视图的手势识别器同时识别。

例子:

//This is needed because the public UIScrollView headers
//don't declare the UIGestureRecognizerDelegate protocol:
@interface UIScrollView (GestureRecognition) <UIGestureRecognizerDelegate>
@end

@interface MyScrollView : UIScrollView

@end

@implementation MyScrollView

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

    if (gestureRecognizer == self.panGestureRecognizer) 
        return YES;
     else if ([UIScrollView instancesRespondToSelector:@selector(gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:)]) 
        //Note: UIScrollView currently doesn't implement this method, but this may change...
        return [super gestureRecognizer:gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:otherGestureRecognizer];
    
    return NO; //the default


@end

对水平或所有垂直滚动视图使用这个子类就足够了。

在您尝试以这种方式使用它之后,您实际上可能会发现您更喜欢默认行为。允许两个视图同时滚动几乎总是会导致在左右滑动时意外垂直滚动,这可能会很烦人(大多数人不会做完美的水平滑动手势)。

【讨论】:

感谢您的回答,我不知道 shouldRecognizeSimultaneously... 我现在就试试。 所以...您是在建议我同时滚动而不是像问题那样阻止其中一个(垂直)滚动,对吧? 看起来很酷(是的!),但这不是这个问题要问的。虽然我正在考虑如何将这个技巧用于预防 在水平视图滚动时禁用垂直视图中的滚动应该不会太难,但是你会遇到相反的问题(即,在水平滚动方向上“卡住” )。【参考方案3】:

我对你的设置有点困惑,但你可能想看看-[UIScrollView setDelaysContentTouches:]。在处理嵌套滚动视图时,您可以使用它来添加一些优先级。

【讨论】:

您解决方案的简单性让我认为我所做的完全错了,我想尽快尝试您的解决方案。但是...我设备上的测试表明它并不能防止偶尔垂直拖动内部滚动视图。所以,我的解决方案在可用性方面仍然做得更好,虽然它看起来像一个 hack。 另一个选项是我没有完全理解你的设置。作为参考,这里是我放在一起研究这个的演示应用程序。 cl.ly/0Y400J3w183a 是的,您的理解是正确的。我相信我不需要调整我的配置文件来在我的手机上测试你的示例 - 我相信在我的设置上尝试 setDelaysContentTouches: 就足够了,因为它们是相同的! 我可以建议您从以下角度看一下我在回答中所做的事情:我希望水平滚动快速流畅,并且我希望它更高,比如 1.5 x,优先于内部视图的垂直滚动,因此我添加了额外的滑动手势识别器以缩小内部视图接受的可用手势范围:(请参阅下一条评论) ...继续之前的评论:我只需要肯定是顶部或底部滑动的手势,我不需要默认识别的任何其他手势 .panGestureRecognizer 我的垂直内部滚动视图。所有这些都是为了提供平滑的水平滚动,而不会被偶尔的垂直微小滚动打断,这会破坏外部视图水平滚动的平滑度,我认为在这个问题/设置的范围内更重要!【参考方案4】:

一旦我遇到这种情况,我有以下工作要做。

从 UIScrollView 子类化你的两个视图&你会很好的。在您的情况下,我可以看到您的 UINoteView 不是 UIScrollView 的子类,将其从 ScrollView 子类化并从此类中删除 innerScrollView。

【讨论】:

我对我不拥有的问题开放了赏金,不允许我编辑原始问题,因此它可以满足我实际拥有的条件。这造成了混乱:与这个问题的条件相反,我确实有你描述的设置(两种视图都是 UIScrollView 的子类),我仍然有描述的问题。 如果有帮助的话 this 是我放在一起玩的快速示例,我并没有真正注意到滚动有太多问题。但是,我可能误解了您的设置。【参考方案5】:

这是我的做法:

在垂直滚动视图中使用 shouldRecognizeSimultaneouslyWithGestureRecognizer(感谢 @omz!)和自定义滑动手势识别器(参见类似问题

Horizontal scrolling UIScrollView with vertical pan gesture),我有以下设置:

@interface UIScrollView (GestureRecognition) <UIGestureRecognizerDelegate>
@end

@interface OuterHorizontalScrollView : UIScrollView ...
@property (weak) InnerVerticalScrollView *currentView; // Current inner vertical scroll view displayed.
@end

@implementation OuterHorizontalScrollView
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer 
    if (self.currentActiveView.placeVerticalScrollView.dragging == NO) 
        self.currentActiveView.placeVerticalScrollView.scrollEnabled = NO;
        return YES;
     else 
        return NO;
    
    
@end

@interface InnerVerticalScrollView : UIScrollView...
@property UISwipeGestureRecognizer *swipeGestureRecognizer;
@end

@implementation InnerVerticalScrollView
- (id)initWithFrame:(CGRect)frame 
    if (self = [super initWithFrame:frame]) 
        ...
        self.swipeGestureRecognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(handleSwipeGesture:)];
        self.swipeGestureRecognizer.direction = UISwipeGestureRecognizerDirectionDown | UISwipeGestureRecognizerDirectionUp;
        [self addGestureRecognizer:self.swipeGestureRecognizer];
    

    return self;


- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer 
    if (gestureRecognizer == self.panGestureRecognizer && self.scrollEnabled == YES) 
        return YES;
     else if (gestureRecognizer == self.swipeGestureRecognizer) 
        return YES;
     else 
        self.scrollEnabled = NO;
    

    return NO;


- (void)handleSwipeGesture:(UIGestureRecognizer *)sender 
    self.scrollEnabled = YES;


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

这段代码有点hacky:只有当他们的自定义Swipe手势被识别(仅顶部或底部方向)时,它才允许滚动垂直滚动视图,所有其他手势都传递给外部滚动视图,而外部滚动视图又只允许任何手势如果没有任何内部滚动视图被拖动。

注意:滑动手势也适用于缓慢或微小的滑动并不明显,但它确实有效(另请参阅上面引用的问题的 cmets)。

我觉得它可以更容易地完成,也许我稍后会重构它。

无论如何,现在外滚动条可以完美运行 - 它可以水平滚动,而不会偶尔垂直平移内部滚动视图。

后期更新:正如我之前所料,我的解决方案确实包含不必要的代码:请参阅@burik 的答案,虽然部分基于我的解决方案,但它会消除这些代码。

【讨论】:

@Avi Tsadok,如果我的解决方案也适用于您,我很感兴趣。或者,如果您有自己的想法,请发布您的。【参考方案6】:

我认为使用速度来确定滚动方向是一种更好的方法:

- (BOOL)gestureRecognizerShouldBegin:(UIPanGestureRecognizer *)gestureRecognizer 
    CGPoint velocity = [gestureRecognizer velocityInView:gestureRecognizer.view];
    return fabs(velocity.y) > 3 * fabs(velocity.x);

【讨论】:

以上是关于水平 UIScrollView 内部有垂直 UIScrollViews - 滚动外部水平视图时如何防止滚动内部滚动视图?的主要内容,如果未能解决你的问题,请参考以下文章

使用按钮检测水平滚动 UIScrollView 上的垂直滚动

UIScrollView 水平和垂直滚动的策略

UIScrollView 子视图中的水平 UISwipeGestureRecognizer ? (UIScrollView 只需要识别垂直滚动)

将两个 UIScrollView 连接在一起

垂直轨道中的 UIScrollView 水平滚动拇指

将水平和垂直 CAGradientLayer 蒙版应用到 UIScrollView/UICollectionView