90

How can I force a UIScrollView in which paging and scrolling are on to only move vertically or horizontally at a given moment?

My understanding is that the directionalLockEnabled property should achieve this, but a diagonal swipe still causes the view to scroll diagonally instead of restricting motion to a single axis.

Edit: to be clearer, I'd like to allow the user to scroll horizontally OR vertically, but not both simultaneously.

4

21 回答 21

117

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

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

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

第一:嵌套的 UIScrollViews。坦率地说,我还没有看到有人让这个工作。但是我个人并没有足够努力,我的实践表明,当你足够努力时,你可以让 UIScrollView 做任何你想做的事情。

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

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

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

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

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

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

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

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

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

请注意,它是 UIScrollView 从 CocoaTouch 接收所有 touchesBegintouchesMoved和事件touchesEndedtouchesCanceled因为它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的事件,直到你决定水平滚动。NotouchesMoved表示不滚动,所以这个首字母touchesBegan不会造成任何伤害。但是一定要设置delaysContentTouches为 NO,这样不会有额外的惊喜计时器干扰。

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

  • 鉴于你总是向前touchesBegan,你也必须向前touchesCancelledtouchesEnded。但是,您必须touchesEnded变成touchesCancelled,因为 UIScrollView 会将touchesBegan,touchesEnded序列解释为触摸单击,并将其转发到内部视图。您已经自己转发了正确的事件,因此您永远不希望 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委托方法动态切换其属性。

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

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

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

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

PS 如果你得到这些工作并且想要分享,请将相关代码通过电子邮件发送到 andreyvit@gmail.com,我很乐意将它包含在我的ScrollingMadness 技巧包中

PPS 将这篇小文章添加到 ScrollingMadness README。

于 2009-05-10T03:28:18.543 回答
56

这对我每次都有效...

scrollView.contentSize = CGSizeMake(scrollView.frame.size.width * NumberOfPages, 1);
于 2010-07-07T18:51:25.137 回答
28

对于像我这样的懒人:

设置scrollview.directionalLockEnabledYES

- (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;
}
于 2013-11-12T12:17:23.473 回答
15

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

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

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;
    }
 }
于 2010-11-09T15:41:07.410 回答
13

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

我想显示一屏内容,但允许用户滚动到其他 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);
    }
}
于 2010-03-04T01:04:41.037 回答
4

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

于 2010-06-05T19:26:30.587 回答
3

Here is my implementation of a paged UIScrollView that only allow orthogonal scrolls. Be sure to set pagingEnabled to 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
于 2011-08-28T13:59:58.980 回答
3

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

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

为了将来参考,我以 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
        }

    }

}

希望这可以帮助。

于 2015-10-21T12:39:14.940 回答
2

我所做的是创建一个带有视图屏幕大小的框架的分页 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;  
}           
于 2011-09-12T18:08:07.493 回答
2

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

self.scrollView.contentSize = CGSizeMake(self.scrollView.contentSize.width, 1);
于 2010-08-07T17:25:05.527 回答
2

这是 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;
}
于 2015-07-15T05:34:50.883 回答
1

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

于 2012-07-10T09:58:12.973 回答
1

From the docs:

"the default value is NO, which means that scrolling is permitted in both horizontal and vertical directions. If the value is YES and the user begins dragging in one general direction (horizontally or vertically), the scroll view disables scrolling in the other direction."

I think the important part is "if... the user begins dragging in one general direction". So if they begin dragging diagonally this doesn't kick in. Not that these docs are always reliable when it comes to interpretation - but that does seem to fit with what you are seeing.

This actually seems sensible. I have to ask - why do you want to restrict to only horizontal or only vertical at any time? Perhaps UIScrollView is not the tool for you?

于 2009-04-08T00:28:55.253 回答
1

需要在和中重置_isHorizontalScroll为 NO 。touchesEndedtouchesCancelled

于 2009-10-09T07:04:55.297 回答
1

我的视图由 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];
    } 
}

对我来说就像一个魅力!

于 2010-08-17T11:02:34.297 回答
1

这是本杰明答案的Swift 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
    }
}
于 2021-05-31T10:39:07.327 回答
0

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

于 2009-06-09T06:05:33.217 回答
0

对于那些在@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

希望对新人有用!

于 2015-06-28T12:00:18.977 回答
0

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

注意:使用此解决方案,scrollView 将忽略每个对角线的平移。

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
    }
}
于 2019-09-04T06:46:56.057 回答
-3

If you are using interface builder to design your interface - this could be done quite easily.

In interface builder simply click on the UIScrollView and select the option (in the inspector) that limits it to only one way scrolling.

于 2009-04-07T23:47:52.730 回答