【问题标题】:UIScrollView: paging horizontally, scrolling vertically?UIScrollView:水平分页,垂直滚动?
【发布时间】:2010-10-18 04:47:59
【问题描述】:

如何强制启用分页和滚动的UIScrollView 仅在给定时刻垂直或水平移动?

我的理解是 directionalLockEnabled 属性应该实现这一点,但对角滑动仍然会导致视图对角滚动,而不是将运动限制在单个轴上。

编辑:为了更清楚,我想允许用户水平或垂直滚动​​,但不能同时滚动。

【问题讨论】:

  • 您想限制视图仅滚动水平/垂直,还是希望用户滚动水平或垂直,但不能同时滚动?
  • 你能帮我一个忙,把标题改一下,让它看起来更有趣和不平凡吗?类似“UIScrollView:水平分页,垂直滚动?”
  • 不幸的是,我以未注册用户的身份在我不再有权访问的计算机上发布了这个问题(在我回到家时以同名注册之前),这意味着我无法编辑它。这也应该解释我向下跟进而不是编辑原始问题。抱歉这次破坏了 Stack 礼仪!

标签: ios objective-c uiscrollview scroll-paging


【解决方案1】:

我必须说,你的处境非常艰难。

请注意,您需要使用带有pagingEnabled=YES 的 UIScrollView 在页面之间切换,但您需要使用pagingEnabled=NO 来垂直滚动。

有两种可能的策略。我不知道哪一个会起作用/更容易实现,所以两个都试试。

第一: 嵌套的 UIScrollViews。坦率地说,我还没有看到有人让这个工作。不过我个人还不够努力,我的实践证明,当你够努力的时候,你可以make UIScrollView do anything you want

所以策略是让外层滚动视图只处理水平滚动,而内层滚动视图只处理垂直滚动。要做到这一点,您必须知道 UIScrollView 在内部是如何工作的。它覆盖hitTest 方法并始终返回自身,以便所有触摸事件都进入UIScrollView。然后在touchesBegantouchesMoved 等内部检查它是否对事件感兴趣,然后处理或将其传递给内部组件。

为了决定触摸是被处理还是被转发,UIScrollView 在你第一次触摸它时启动一个计时器:

  • 如果您的手指在 150 毫秒内没有明显移动,它会将事件传递到内部视图。

  • 如果您在 150 毫秒内显着移动了手指,它就会开始滚动(并且永远不会将事件传递到内部视图)。

    请注意,当您触摸表格(它是滚动视图的子类)并立即开始滚动时,您所触摸的行永远不会突出显示。

  • 如果您没有在 150 毫秒内显着移动手指并且 UIScrollView 开始将事件传递给内部视图,但是 那么您已经将手指移动得足够远以进行滚动首先,UIScrollView 在内部视图上调用 touchesCancelled 并开始滚动。

    请注意,当您触摸表格时,稍微按住手指然后开始滚动,您触摸的行首先突出显示,然后取消突出显示。

这些事件的顺序可以通过 UIScrollView 的配置来改变:

  • 如果 delaysContentTouches 为 NO,则不使用计时器 - 事件立即转到内部控件(但如果您将手指移动得足够远,则会取消)
  • 如果cancelsTouches 为NO,则一旦将事件发送到控件,将永远不会发生滚动。

请注意,UIScrollView 从 CocoaTouch 接收 all touchesBegintouchesMovedtouchesEndedtouchesCanceled 事件(因为它的 hitTest 告诉它这样做)。然后,只要它愿意,它就会将它们转发到内部视图。

现在您已经了解了有关 UIScrollView 的一切,您可以更改它的行为。我敢打赌你想优先考虑垂直滚动,这样一旦用户触摸视图并开始移动他的手指(即使是轻微的),视图就会开始在垂直方向滚动;但是当用户在水平方向上移动手指足够远时,你想取消垂直滚动并开始水平滚动。

您希望将外部 UIScrollView 子类化(例如,将您的类命名为 RemorsefulScrollView),以便立即将所有事件转发到内部视图,而不是默认行为,并且只有在检测到显着水平移动时才会滚动。

如何让 RemorsefulScrollView 表现得那样?

  • 看起来禁用垂直滚动并将delaysContentTouches 设置为 NO 应该可以使嵌套的 UIScrollViews 工作。不幸的是,它没有; UIScrollView 似乎对快速运动(无法禁用)进行了一些额外的过滤,因此即使 UIScrollView 只能水平滚动,它也总是会吃掉(并忽略)足够快的垂直运动。

    效果非常严重,以至于嵌套滚动视图内的垂直滚动无法使用。 (看起来你已经完全掌握了这个设置,所以试试吧:按住手指 150 毫秒,然后垂直移动它——嵌套的 UIScrollView 会按预期工作!)

  • 这意味着您不能使用 UIScrollView 的代码进行事件处理;您必须覆盖 RemorsefulScrollView 中的所有四种触摸处理方法并首先进行自己的处理,如果您决定使用水平滚动,则仅将事件转发到 super (UIScrollView)。

  • 但是,您必须将touchesBegan 传递给 UIScrollView,因为您希望它记住一个基坐标以供将来水平滚动(如果您以后确定它水平滚动)。稍后您将无法将 touchesBegan 发送到 UIScrollView,因为您无法存储 touches 参数:它包含将在下一个 touchesMoved 事件之前发生变异的对象,并且您无法重现旧状态。

    所以你必须立即将touchesBegan 传递给 UIScrollView,但是你会隐藏任何进一步的touchesMoved 事件,直到你决定水平滚动。没有touchesMoved 表示没有滚动,所以这个初始的touchesBegan 不会造成伤害。但请务必将 delaysContentTouches 设置为 NO,这样不会有额外的惊喜计时器干扰。

    (Offtopic — 与你不同,UIScrollView 可以正确存储触摸,并且可以在以后重现和转发原始的touchesBegan 事件。它使用未发布的 API 具有不公平的优势,因此可以在之前克隆触摸对象它们是变异的。)

  • 鉴于您总是转发touchesBegan,您还必须转发touchesCancelledtouchesEnded。但是,您必须将touchesEnded 转换为touchesCancelled,因为UIScrollView 会将touchesBegantouchesEnded 序列解释为触摸单击,并将其转发到内部视图。您已经自己转发了正确的事件,因此您永远不希望 UIScrollView 转发任何内容。

基本上这里是您需要做的伪代码。为简单起见,我从不允许在多点触控事件发生后进行水平滚动。

// RemorsefulScrollView.h

@interface RemorsefulScrollView : UIScrollView {
  CGPoint _originalPoint;
  BOOL _isHorizontalScroll, _isMultitouch;
  UIView *_currentChild;
}
@end

// RemorsefulScrollView.m

// the numbers from an example in Apple docs, may need to tune them
#define kThresholdX 12.0f
#define kThresholdY 4.0f

@implementation RemorsefulScrollView

- (id)initWithFrame:(CGRect)frame {
  if (self = [super initWithFrame:frame]) {
    self.delaysContentTouches = NO;
  }
  return self;
}

- (id)initWithCoder:(NSCoder *)coder {
  if (self = [super initWithCoder:coder]) {
    self.delaysContentTouches = NO;
  }
  return self;
}

- (UIView *)honestHitTest:(CGPoint)point withEvent:(UIEvent *)event {
  UIView *result = nil;
  for (UIView *child in self.subviews)
    if ([child pointInside:point withEvent:event])
      if ((result = [child hitTest:point withEvent:event]) != nil)
        break;
  return result;
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    [super touchesBegan:touches withEvent:event]; // always forward touchesBegan -- there's no way to forward it later
    if (_isHorizontalScroll)
      return; // UIScrollView is in charge now
    if ([touches count] == [[event touchesForView:self] count]) { // initial touch
      _originalPoint = [[touches anyObject] locationInView:self];
    _currentChild = [self honestHitTest:_originalPoint withEvent:event];
    _isMultitouch = NO;
    }
  _isMultitouch |= ([[event touchesForView:self] count] > 1);
  [_currentChild touchesBegan:touches withEvent:event];
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
  if (!_isHorizontalScroll && !_isMultitouch) {
    CGPoint point = [[touches anyObject] locationInView:self];
    if (fabsf(_originalPoint.x - point.x) > kThresholdX && fabsf(_originalPoint.y - point.y) < kThresholdY) {
      _isHorizontalScroll = YES;
      [_currentChild touchesCancelled:[event touchesForView:self] withEvent:event]
    }
  }
  if (_isHorizontalScroll)
    [super touchesMoved:touches withEvent:event]; // UIScrollView only kicks in on horizontal scroll
  else
    [_currentChild touchesMoved:touches withEvent:event];
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
  if (_isHorizontalScroll)
    [super touchesEnded:touches withEvent:event];
    else {
    [super touchesCancelled:touches withEvent:event];
    [_currentChild touchesEnded:touches withEvent:event];
    }
}

- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
  [super touchesCancelled:touches withEvent:event];
  if (!_isHorizontalScroll)
    [_currentChild touchesCancelled:touches withEvent:event];
}

@end

我没有尝试运行甚至编译它(并在纯文本编辑器中输入了整个类),但您可以从上面开始,希望它能够正常工作。

我看到的唯一隐藏的问题是,如果您将任何非 UIScrollView 子视图添加到 RemorsefulScrollView,那么您转发给孩子的触摸事件可能会通过响应者链返回给您,如果孩子并不总是处理像 UIScrollView 这样的触摸做。一个防弹的 RemorsefulScrollView 实现可以防止 touchesXxx 重入。

第二种策略:如果由于某种原因嵌套的 UIScrollViews 不能正常工作或证明太难正确,您可以尝试只使用一个 UIScrollView,将其 pagingEnabled 属性打开从你的 scrollViewDidScroll 委托方法飞。

为了防止对角滚动,您应该首先尝试记住 scrollViewWillBeginDragging 中的 contentOffset,如果检测到对角移动,请检查并重置 scrollViewDidScroll 中的 contentOffset。另一种尝试的策略是将 contentSize 重置为仅在一个方向上启用滚动,一旦您决定用户手指的方向。 (UIScrollView 似乎很宽容地从它的委托方法中摆弄 contentSize 和 contentOffset。)

如果这也不起作用或导致视觉效果草率,您必须覆盖touchesBegantouchesMoved 等,而不是将对角线移动事件转发到 UIScrollView。 (但是,在这种情况下,用户体验将不是最理想的,因为您将不得不忽略对角线的移动,而不是强迫它们向一个方向移动。如果您真的很喜欢冒险,您可以编写自己的 UITouch 类似的东西,比如 RevengeTouch。目标-C 是普通的旧 C,世界上没有比 C 更鸭式的了;只要没有人检查对象的真实类,我相信没有人这样做,你可以让任何类看起来像任何其他类。这打开了可以用你想要的任何坐标来合成你想要的任何触摸。)

备份策略: TTScrollView 是 Three20 library 中 UIScrollView 的合理重新实现。不幸的是,它对用户来说感觉非常不自然和非 iphonish。但是如果使用 UIScrollView 的每一次尝试都失败了,你可以回退到自定义编码的滚动视图。如果可能的话,我建议不要这样做;使用 UIScrollView 可确保您获得本机外观,无论它在未来的 iPhone OS 版本中如何发展。

好的,这篇小论文有点太长了。几天前完成了 ScrollingMadness 的工作后,我仍然喜欢 UIScrollView 游戏。

附:如果你得到这些工作并想分享,请将相关代码通过电子邮件发送到 andreyvit@gmail.com,我很乐意将其包含在我的 ScrollingMadness bag of tricks 中。

P.P.S.将这篇小文章添加到 ScrollingMadness README。

【讨论】:

  • 请注意,这种嵌套的滚动视图行为现在在 SDK 中开箱即用,不需要有趣的业务
  • +1'd andygeers 评论,然后意识到它不准确;您仍然可以通过从起点对角拖动来“破坏”常规 UIScrollView,然后您“拉动滚动条丢失”,它会忽略方向锁定,直到您释放并最终天知道在哪里。
  • 感谢伟大的幕后解释!我选择了下面的 Mattias Wadman 的解决方案,它看起来对 IMO 的侵入性较小。
  • 现在 UIScrollView 公开了它的平移手势识别器,很高兴看到这个答案得到更新。 (但也许这超出了原始范围。)
  • 这篇文章有更新的可能吗?很棒的帖子,感谢您的意见。
【解决方案2】:

这对我每次都有效...

scrollView.contentSize = CGSizeMake(scrollView.frame.size.width * NumberOfPages, 1);

【讨论】:

  • 对于其他偶然发现这个问题的人:我认为这个答案是在澄清问题的编辑之前发布的。这将允许您水平滚动,它不能解决能够垂直或水平滚动但不能对角滚动的问题。
  • 对我来说就像一个魅力。我有一个很长的带分页的水平滚动视图,每个页面上都有一个 UIWebView,它处理我需要的垂直滚动。
  • 太棒了!但是有人可以解释这是如何工作的(将 contentSize 高度设置为 1)!
  • 它确实有效,但它为什么以及如何工作?任何人都可以理解它吗?
【解决方案3】:

对于像我这样的懒人:

scrollview.directionalLockEnabled 设置为YES

- (void) scrollViewWillBeginDragging: (UIScrollView *) scrollView
{
    self.oldContentOffset = scrollView.contentOffset;
}

- (void) scrollViewDidScroll: (UIScrollView *) scrollView
{
    if (scrollView.contentOffset.x != self.oldContentOffset.x)
    {
        scrollView.pagingEnabled = YES;
        scrollView.contentOffset = CGPointMake(scrollView.contentOffset.x,
                                               self.oldContentOffset.y);
    }
    else
    {
        scrollView.pagingEnabled = NO;
    }
}

- (void) scrollViewDidEndDecelerating: (UIScrollView *) scrollView
{
    self.oldContentOffset = scrollView.contentOffset;
}

【讨论】:

    【解决方案4】:

    我使用以下方法解决了这个问题,希望对您有所帮助。

    首先在你的控制器头文件中定义以下变量。

    CGPoint startPos;
    int     scrollDirection;
    
    当您的代理收到 scrollViewWillBeginDragging 消息时,

    startPos 将保留 contentOffset 值。所以在这个方法中我们这样做了;

    - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView{
        startPos = scrollView.contentOffset;
        scrollDirection=0;
    }
    

    然后我们使用这些值来确定用户在 scrollViewDidScroll 消息中的预期滚动方向。

    - (void)scrollViewDidScroll:(UIScrollView *)scrollView{
    
       if (scrollDirection==0){//we need to determine direction
           //use the difference between positions to determine the direction.
           if (abs(startPos.x-scrollView.contentOffset.x)<abs(startPos.y-scrollView.contentOffset.y)){          
              NSLog(@"Vertical Scrolling");
              scrollDirection=1;
           } else {
              NSLog(@"Horitonzal Scrolling");
              scrollDirection=2;
           }
        }
    //Update scroll position of the scrollview according to detected direction.     
        if (scrollDirection==1) {
           [scrollView setContentOffset:CGPointMake(startPos.x,scrollView.contentOffset.y) animated:NO];
        } else if (scrollDirection==2){
           [scrollView setContentOffset:CGPointMake(scrollView.contentOffset.x,startPos.y) animated:NO];
        }
     }
    

    最后我们必须在用户端拖动时停止所有更新操作;

     - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate{
        if (decelerate) {
           scrollDirection=3;
        }
     }
    

    【讨论】:

    • 嗯...实际上,经过更多调查 - 它工作相当很好,但问题是它在减速阶段失去了对方向的控制 - 所以如果你开始关闭对角线移动手指,然后它似乎只会水平或垂直移动直到你放开,此时它会在对角线方向上减速一段时间
    • @andygeers,如果将- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate{ if (decelerate) { scrollDirection=3; } } 更改为- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate{ if (!decelerate) { scrollDirection=0; } }- (void)scrollViewDidEndDecelerating{ scrollDirection=0; } 可能会更好
    • 对我不起作用。在飞行中设置 contentOffset 似乎会破坏分页行为。我选择了 Mattias Wadman 的解决方案。
    【解决方案5】:

    我可能在这里挖坟,但我今天偶然发现了这篇文章,因为我试图解决同样的问题。这是我的解决方案,似乎效果很好。

    我想显示一屏内容,但允许用户上下左右滚动到其他 4 个全屏内容之一。我有一个大小为 320x480 且 contentSize 为 960x1440 的 UIScrollView。内容偏移量从 320,480 开始。在 scrollViewDidEndDecelerating: 中,我重绘了 5 个视图(中心视图和周围的 4 个视图),并将内容偏移量重置为 320,480。

    这是我的解决方案的核心,scrollViewDidScroll: 方法。

    - (void)scrollViewDidScroll:(UIScrollView *)scrollView
    {   
        if (!scrollingVertically && ! scrollingHorizontally)
        {
            int xDiff = abs(scrollView.contentOffset.x - 320);
            int yDiff = abs(scrollView.contentOffset.y - 480);
    
            if (xDiff > yDiff)
            {
                scrollingHorizontally = YES;
            }
            else if (xDiff < yDiff)
            {
                scrollingVertically = YES;
            }
        }
    
        if (scrollingHorizontally)
        {
            scrollView.contentOffset = CGPointMake(scrollView.contentOffset.x, 480);
        }
        else if (scrollingVertically)
        {
            scrollView.contentOffset = CGPointMake(320, scrollView.contentOffset.y);
        }
    }
    

    【讨论】:

    • 您可能会发现,当用户放开滚动视图时,这段代码并没有很好地“减速”——它会停止。如果你不想减速,这对你来说可能没问题。
    【解决方案6】:

    伙计们,这就像使子视图高度与滚动视图高度和内容大小高度一样简单。然后垂直滚动被禁用。

    【讨论】:

    • 对于其他偶然发现这个问题的人:我认为这个答案是在澄清问题的编辑之前发布的。这将只允许您水平滚动,它不能解决能够垂直或水平滚动但不能对角滚动的问题。
    【解决方案7】:

    这是我实现的分页UIScrollView,它只允许正交滚动。请务必将pagingEnabled 设置为YES

    PagedOrthoScrollView.h

    @interface PagedOrthoScrollView : UIScrollView
    @end
    

    PagedOrthoScrollView.m

    #import "PagedOrthoScrollView.h"
    
    typedef enum {
      PagedOrthoScrollViewLockDirectionNone,
      PagedOrthoScrollViewLockDirectionVertical,
      PagedOrthoScrollViewLockDirectionHorizontal
    } PagedOrthoScrollViewLockDirection; 
    
    @interface PagedOrthoScrollView ()
    @property(nonatomic, assign) PagedOrthoScrollViewLockDirection dirLock;
    @property(nonatomic, assign) CGFloat valueLock;
    @end
    
    @implementation PagedOrthoScrollView
    @synthesize dirLock;
    @synthesize valueLock;
    
    - (id)initWithFrame:(CGRect)frame {
      self = [super initWithFrame:frame];
      if (self == nil) {
        return self;
      }
    
      self.dirLock = PagedOrthoScrollViewLockDirectionNone;
      self.valueLock = 0;
    
      return self;
    }
    
    - (void)setBounds:(CGRect)bounds {
      int mx, my;
    
      if (self.dirLock == PagedOrthoScrollViewLockDirectionNone) {
        // is on even page coordinates, set dir lock and lock value to closest page 
        mx = abs((int)CGRectGetMinX(bounds) % (int)CGRectGetWidth(self.bounds));
        if (mx != 0) {
          self.dirLock = PagedOrthoScrollViewLockDirectionHorizontal;
          self.valueLock = (round(CGRectGetMinY(bounds) / CGRectGetHeight(self.bounds)) *
                            CGRectGetHeight(self.bounds));
        } else {
          self.dirLock = PagedOrthoScrollViewLockDirectionVertical;
          self.valueLock = (round(CGRectGetMinX(bounds) / CGRectGetWidth(self.bounds)) *
                            CGRectGetWidth(self.bounds));
        }
    
        // show only possible scroll indicator
        self.showsVerticalScrollIndicator = dirLock == PagedOrthoScrollViewLockDirectionVertical;
        self.showsHorizontalScrollIndicator = dirLock == PagedOrthoScrollViewLockDirectionHorizontal;
      }
    
      if (self.dirLock == PagedOrthoScrollViewLockDirectionHorizontal) {
        bounds.origin.y = self.valueLock;
      } else {
        bounds.origin.x = self.valueLock;
      }
    
      mx = abs((int)CGRectGetMinX(bounds) % (int)CGRectGetWidth(self.bounds));
      my = abs((int)CGRectGetMinY(bounds) % (int)CGRectGetHeight(self.bounds));
    
      if (mx == 0 && my == 0) {
        // is on even page coordinates, reset lock
        self.dirLock = PagedOrthoScrollViewLockDirectionNone;
      }
    
      [super setBounds:bounds];
    }
    
    @end
    

    【讨论】:

    • 在 iOS 7 上也很好用。谢谢!
    【解决方案8】:

    我也不得不解决这个问题,虽然 Andrey Tarantsov 的回答肯定有很多有用的信息可以帮助理解 UIScrollViews 的工作原理,但我觉得解决方案有点过于复杂。我的解决方案,不涉及任何子类化或触摸转发,如下:

    1. 创建两个嵌套滚动视图,每个方向一个。
    2. 创建两个虚拟UIPanGestureRecognizers,每个方向再创建一个。
    3. 使用UIGestureRecognizer's -requireGestureRecognizerToFail: 选择器在UIScrollViews' panGestureRecognizer 属性和对应于垂直于UIScrollView 所需滚动方向的方向的虚拟UIPanGestureRecognizer 之间创建失败依赖关系。
    4. 在您的虚拟 UIPanGestureRecognizers 的 -gestureRecognizerShouldBegin: 回调中,使用手势的 -translationInView: 选择器计算平移的初始方向(您的 UIPanGestureRecognizers 不会调用此委托回调,直到您的触摸转换到足以注册为平底锅)。根据计算的方向允许或禁止您的手势识别器开始,这反过来应该控制是否允许垂直 UIScrollView 平移手势识别器开始。
    5. -gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer: 中,不要让你的虚拟UIPanGestureRecognizers 在滚动的同时运行(除非你想添加一些额外的行为)。

    【讨论】:

      【解决方案9】:

      为了将来参考,我以 Andrey Tanasov 的回答为基础,提出了以下可以正常工作的解决方案。

      首先定义一个变量来存储contentOffset,并设置为0,0坐标

      var positionScroll = CGPointMake(0, 0)
      

      现在在 scrollView 委托中实现以下内容:

      override func scrollViewWillBeginDragging(scrollView: UIScrollView) {
          // Note that we are getting a reference to self.scrollView and not the scrollView referenced passed by the method
          // For some reason, not doing this fails to get the logic to work
          self.positionScroll = (self.scrollView?.contentOffset)!
      }
      
      override func scrollViewDidScroll(scrollView: UIScrollView) {
      
          // Check that contentOffset is not nil
          // We do this because the scrollViewDidScroll method is called on viewDidLoad
          if self.scrollView?.contentOffset != nil {
      
              // The user wants to scroll on the X axis
              if self.scrollView?.contentOffset.x > self.positionScroll.x || self.scrollView?.contentOffset.x < self.positionScroll.x {
      
                  // Reset the Y position of the scrollView to what it was BEFORE scrolling started
                  self.scrollView?.contentOffset = CGPointMake((self.scrollView?.contentOffset.x)!, self.positionScroll.y);
                  self.scrollView?.pagingEnabled = true
              }
      
              // The user wants to scroll on the Y axis
              else {
                  // Reset the X position of the scrollView to what it was BEFORE scrolling started
                  self.scrollView?.contentOffset = CGPointMake(self.positionScroll.x, (self.scrollView?.contentOffset.y)!);
                  self.collectionView?.pagingEnabled = false
              }
      
          }
      
      }
      

      希望这会有所帮助。

      【讨论】:

        【解决方案10】:

        我所做的是创建一个带有视图屏幕大小的框架的分页 UIScrollView,并将内容大小设置为我所有内容所需的宽度和 1 的高度(感谢 Tonetel)。然后我创建了菜单 UIScrollView 页面,每个页面都设置了框架,视图屏幕的大小,并将每个页面的内容大小设置为屏幕的宽度和每个页面的必要高度。然后我简单地将每个菜单页面添加到分页视图中。确保在分页滚动视图上启用分页但在菜单视图上禁用(默认情况下应该禁用),你应该很高兴。

        分页滚动视图现在水平滚动,但由于内容高度而不是垂直滚动。由于内容大小的限制,每个页面都会垂直滚动,但不会水平滚动。

        这里是代码,如果该解释还有一些不足之处:

        UIScrollView *newPagingScrollView =  [[UIScrollView alloc] initWithFrame:self.view.bounds]; 
        [newPagingScrollView setPagingEnabled:YES];
        [newPagingScrollView setShowsVerticalScrollIndicator:NO];
        [newPagingScrollView setShowsHorizontalScrollIndicator:NO];
        [newPagingScrollView setDelegate:self];
        [newPagingScrollView setContentSize:CGSizeMake(self.view.bounds.size.width * NumberOfDetailPages, 1)];
        [self.view addSubview:newPagingScrollView];
        
        float pageX = 0;
        
        for (int i = 0; i < NumberOfDetailPages; i++)
        {               
            CGRect pageFrame = (CGRect) 
            {
                .origin = CGPointMake(pageX, pagingScrollView.bounds.origin.y), 
                .size = pagingScrollView.bounds.size
            };
            UIScrollView *newPage = [self createNewPageFromIndex:i ToPageFrame:pageFrame]; // newPage.contentSize custom set in here        
            [pagingScrollView addSubview:newPage];
        
            pageX += pageFrame.size.width;  
        }           
        

        【讨论】:

          【解决方案11】:

          感谢 Tonetel。我稍微修改了您的方法,但这正是防止水平滚动所需要的。

          self.scrollView.contentSize = CGSizeMake(self.scrollView.contentSize.width, 1);
          

          【讨论】:

            【解决方案12】:

            这是 icompot 的改进解决方案,对我来说非常不稳定。这个很好用,而且很容易实现:

            - (void) scrollViewWillBeginDragging: (UIScrollView *) scrollView
            {
                self.oldContentOffset = scrollView.contentOffset;
            }
            
            - (void) scrollViewDidScroll: (UIScrollView *) scrollView
            {    
            
                float XOffset = fabsf(self.oldContentOffset.x - scrollView.contentOffset.x );
                float YOffset = fabsf(self.oldContentOffset.y - scrollView.contentOffset.y );
            
                    if (scrollView.contentOffset.x != self.oldContentOffset.x && (XOffset >= YOffset) )
                    {
            
                        scrollView.pagingEnabled = YES;
                        scrollView.contentOffset = CGPointMake(scrollView.contentOffset.x,
                                                               self.oldContentOffset.y);
            
                    }
                    else
                    {
                        scrollView.pagingEnabled = NO;
                           scrollView.contentOffset = CGPointMake( self.oldContentOffset.x,
                                                               scrollView.contentOffset.y);
                    }
            
            }
            
            
            - (void) scrollViewDidEndDecelerating: (UIScrollView *) scrollView
            {
                self.oldContentOffset = scrollView.contentOffset;
            }
            

            【讨论】:

              【解决方案13】:

              来自文档:

              "默认值为 NO,这意味着允许在水平和垂直方向上滚动。如果值为 YES 并且用户开始在一个大致方向(水平或垂直)拖动,则滚动视图禁用滚动视图其他方向。”

              我认为重要的部分是“如果...用户开始朝着一个大方向拖动”。 因此,如果他们开始对角拖动,这不会生效。 并不是说这些文档在解释方面总是可靠的 - 但这似乎与您所看到的相符。

              这实际上似乎是明智的。我不得不问 - 你为什么要在任何时候限制为仅水平或仅垂直?也许 UIScrollView 不是适合你的工具?

              【讨论】:

                【解决方案14】:

                我的视图由 10 个水平视图组成,其中一些视图由多个垂直视图组成,所以你会得到这样的结果:

                
                1.0   2.0   3.1    4.1
                      2.1   3.1
                      2.2
                      2.3

                在水平和垂直轴上启用分页

                视图还具有以下属性:

                [self setDelaysContentTouches:NO];
                [self setMultipleTouchEnabled:NO];
                [self setDirectionalLockEnabled:YES];

                现在要防止对角滚动,请执行以下操作:

                -(void)scrollViewDidScroll:(UIScrollView *)scrollView {
                    int pageWidth = 768;
                    int pageHeight = 1024;
                    int subPage =round(self.contentOffset.y / pageHeight);
                    if ((int)self.contentOffset.x % pageWidth != 0 && (int)self.contentOffset.y % pageHeight != 0) {
                        [self setContentOffset:CGPointMake(self.contentOffset.x, subPage * pageHeight];
                    } 
                }
                

                对我来说就像一个魅力!

                【讨论】:

                • 不明白这对你有什么用...你能上传一个示例项目吗?
                【解决方案15】:

                需要将touchesEndedtouchesCancelled 中的_isHorizontalScroll 重置为NO。

                【讨论】:

                  【解决方案16】:

                  如果你有一个大滚动视图...并且你想限制对角滚动http://chandanshetty01.blogspot.in/2012/07/restricting-diagonal-scrolling-in.html

                  【讨论】:

                    【解决方案17】:

                    这是Benjamin's answerSwift 5 版本

                    // add this property to your view controller
                    var positionScroll: CGPoint = .zero
                    
                    // make sure to set isDirectionalLockEnabled to true on your scroll view
                    self.scrollView.isDirectionalLockEnabled = true
                    
                    // then in scrollview/collectionview/tableview delegate
                    func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
                        positionScroll = scrollView.contentOffset
                    }
                    
                    func scrollViewDidScroll(_ scrollView: UIScrollView) {
                        // The user wants to scroll on the X axis
                        if scrollView.contentOffset.x > positionScroll.x || scrollView.contentOffset.x < positionScroll.x {
                            // Reset the Y position of the scrollView to what it was before scrolling started
                            scrollView.contentOffset = CGPoint(x: scrollView.contentOffset.x, y: positionScroll.y)
                            scrollView.isPagingEnabled = true
                        } else {
                            // The user wants to scroll on the Y axis
                            // Reset the X position of the scrollView to what it was before scrolling started
                            scrollView.contentOffset = CGPoint(x: positionScroll.x, y: scrollView.contentOffset.y)
                            scrollView.isPagingEnabled = false
                        }
                    }
                    

                    【讨论】:

                      【解决方案18】:

                      如果您还没有在 3.0 测试版中尝试过这样做,我建议您试一试。我目前正在滚动视图中使用一系列表格视图,它的工作方式与预期一样。 (好吧,无论如何,滚动确实如此。在寻找完全不同问题的解决方案时发现了这个问题......)

                      【讨论】:

                        【解决方案19】:

                        对于那些在@AndreyTarantsov 第二个解决方案中使用 Swift 的人来说,代码如下:

                            var positionScroll:CGFloat = 0
                        
                            func scrollViewWillBeginDragging(scrollView: UIScrollView) {
                                positionScroll = self.theScroll.contentOffset.x
                            }
                        
                            func scrollViewDidScroll(scrollView: UIScrollView) {
                                if self.theScroll.contentOffset.x > self.positionScroll || self.theScroll.contentOffset.x < self.positionScroll{
                                     self.theScroll.pagingEnabled = true
                                }else{
                                    self.theScroll.pagingEnabled = false
                                }
                            }
                        

                        我们在这里所做的是在滚动视图被拖动之前保持当前位置,然后检查 x 与 x 的当前位置相比是否增加或减少。如果是,则将 pagingEnabled 设置为 true,如果否(y 增加/减少)则将 pagingEnabled 设置为 false

                        希望对新手有用!

                        【讨论】:

                          【解决方案20】:

                          我设法通过实现 scrollViewDidBeginDragging(_:) 并查看底层 UIPanGestureRecognizer 上的速度来解决这个问题。

                          注意:使用此解决方案,滚动视图将忽略每个对角线平移。

                          override func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
                              super.scrollViewWillBeginDragging(scrollView)
                              let velocity = scrollView.panGestureRecognizer.velocity(in: scrollView)
                              if velocity.x != 0 && velocity.y != 0 {
                                  scrollView.panGestureRecognizer.isEnabled = false
                                  scrollView.panGestureRecognizer.isEnabled = true
                              }
                          }
                          

                          【讨论】:

                            【解决方案21】:

                            如果您使用界面生成器来设计界面 - 这可以很容易地完成。

                            在界面构建器中,只需单击 UIScrollView 并选择(在检查器中)将其限制为仅一种方式滚动的选项。

                            【讨论】:

                            • IB 中没有选项可以做到这一点。您可能提到的两个选项与滚动条的显示有关,而不是实际的滚动。此外,方向锁定仅在滚动开始后锁定方向。
                            猜你喜欢
                            • 1970-01-01
                            • 1970-01-01
                            • 1970-01-01
                            • 1970-01-01
                            • 2013-12-25
                            • 1970-01-01
                            • 2023-03-29
                            • 1970-01-01
                            • 1970-01-01
                            相关资源
                            最近更新 更多