UIScrollView:水平分页,垂直滚动?


88

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

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

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


您是否要限制只滚动Horiz / Vert的视图,还是要用户滚动Horiz或Vert,但不能同时滚动两个视图?
Will Hartung

您能帮我一个忙,更改标题,使其看起来更有趣且平凡吗?类似于“ UIScrollView:水平分页,垂直滚动?”
安德烈·塔兰佐夫

不幸的是,我以未注册用户的身份在无法再使用的计算机上发布了问题(在回家之前以相同的名称注册),这意味着我无法对其进行编辑。这也应该解释我从下到下的后续工作,而不是编辑原始问题。抱歉这次破坏了堆栈礼节!
尼克

Answers:


117

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

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

有两种可能的策略。我不知道哪个会工作/更容易实现,因此请同时尝试。

首先:嵌套的UIScrollViews。坦白说,我还没有看到一个能做到这一点的人。但是,我个人还没有足够努力,而实践表明,当您足够努力时,可以使UIScrollView做任何您想做的事情

因此,策略是让外部滚动视图仅处理水平滚动,让内部滚动视图仅处理垂直滚动。为此,您必须知道UIScrollView如何在内部工作。它重写hitTest方法并始终返回自身,以便所有触摸事件都进入UIScrollView。然后在touchesBegantouchesMoved等内部,它检查是否对该事件感兴趣,然后处理该事件或将其传递给内部组件。

要确定是要处理还是要转发触摸,UIScrollView在您首次触摸时会启动一个计时器:

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

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

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

  • 如果你还没有150毫秒内显著移动你的手指的UIScrollView开始传递事件内视图,但随后您移动手指远远不够的滚动开始,UIScrollView的要求touchesCancelled在内部视图和开始滚动。

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

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

  • 如果delaysContentTouches为“否”,则不使用计时器-事件立即进入内部控件(但是如果您将手指移到足够远的位置,事件将被取消)
  • 如果cancelsTouches为“否”,则一旦将事件发送到控件,就永远不会发生滚动。

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

既然您了解了UIScrollView的所有知识,就可以更改其行为。我敢打赌,您希望优先使用垂直滚动,以便一旦用户触摸视图并开始移动手指(甚至略微移动),视图就会开始沿垂直方向滚动;但是当用户在水平方向上移动手指足够远时,您要取消垂直滚动并开始水平滚动。

您想对外部UIScrollView进行子类化(例如,将您的类命名为RemorsefulScrollView),以便代替默认行为将所有事件立即转发到内部视图,并且仅当检测到明显的水平移动时才滚动。

如何使RemorsefulScrollView表现为这种方式?

  • 看起来好像禁用垂直滚动并将其设置delaysContentTouches为NO应该可以使嵌套的UIScrollViews正常工作。不幸的是,事实并非如此。UIScrollView似乎对快速运动(无法禁用)进行了一些附加过滤,因此,即使UIScrollView只能水平滚动,它也总是会消耗(忽略)足够快的垂直运动。

    效果如此严重,以至于嵌套滚动视图中的垂直滚动无法使用。(看来您已经完全设置好了,因此请尝试:手指保持150ms,然后沿垂直方向移动-嵌套的UIScrollView会按预期工作!)

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

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

    因此,您必须touchesBegan立即传递给UIScrollView,但是您将对其隐藏所有其他touchesMoved事件,直到您决定水平滚动。绝不touchesMoved意味着没有滚动,因此此初始名称touchesBegan不会造成任何损害。但是请务必将其设置delaysContentTouches为“否”,这样就不会有其他意外计时器受到干扰。

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

  • 考虑到你一直向前touchesBegan,你也必须向前touchesCancelled和前进touchesEnded。但是,您必须转touchesEndedtouchesCancelled,因为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相处,pagingEnabledscrollViewDidScroll委托方法中即时切换其属性。

为防止对角线滚动,您应该首先尝试记住中的contentOffset scrollViewWillBeginDragging,并在scrollViewDidScroll检测到对角线移动时在内部检查并重置contentOffset 。尝试的另一种策略是,一旦确定用户的手指往哪个方向移动,就将contentSize重置为仅允许在一个方向上滚动。(UIScrollView对于从其委托方法摆弄contentSize和contentOffset似乎很宽容。)

如果这不起作用或导致草率的视觉效果,则必须覆盖touchesBegantouchesMoved等等,并且不将对角线移动事件转发到UIScrollView。(但是,在这种情况下,用户体验将不是最佳的,因为您将不得不忽略对角线移动,而不是将其强制向单个方向移动。如果您真的很冒险,则可以编写自己的UITouch,类似于RevengeTouch。) -C是普通的旧C,并且世界上没有比C更令人沮丧的类型;只要没有人检查对象的真实类(我相信没人会这样做),您就可以使任何类看起来像任何其他类。可以将所需的任何触摸与所需的坐标进行合成。)

备份策略: TTScrollView是Three20库中UIScrollView的明智重新实现。不幸的是,对于用户而言,这感觉非常不自然且不友好。但是,如果每次使用UIScrollView的尝试均失败,则可以退回到自定义编码的滚动视图。我建议尽量不要这样做。使用UIScrollView可以确保您获得本机的外观,无论它在将来的iPhone OS版本中如何发展。

好的,这篇小文章太长了。几天前在ScrollingMadness上工作之后,我仍然仍在学习UIScrollView游戏。

PS:如果您可以使用这些工具中的任何一种,并且希望共享它们,请给我发电子邮件相关的代码:andreyvit@gmail.com,我很乐意将其包含在ScrollingMadness技巧包中

PPS将此小文章添加到ScrollingMadness自述文件中。


7
请注意,此嵌套的scrollview行为现在可以在SDK中立即使用,不需要任何有趣的工作
andygeers

1
+1了andygeers评论,然后意识到它不准确;您仍然可以通过从起点对角拖动来“破坏”常规的UIScrollView,然后“拉动滚动条丢失”,并且它将忽略方向锁定,直到您松开并最终神知道该在哪里。
Kalle

感谢您对幕后的精彩解释!我选择了Mattias Wadman的解决方案,该解决方案在下面看来对IMO的侵入较小。
Ortwin Gentz 2014年

既然UIScrollView公开了其平移手势识别器,那么很高兴看到这个答案得到了更新。(但也许超出了原来的范围。)
jtbandes 2014年

此帖子是否有可能更新?很棒的帖子,谢谢您的投入。
帕万

56

每次都对我有用...

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

20
对于任何其他在此问题上绊脚石的人:我认为此答案已在澄清该问题的编辑之前发布。这将使您只能水平滚动,而不能解决能够垂直或水平滚动但不能对角滚动的问题。
andygeers 2011年

对我来说就像是一种魅力。我有一个很长的带有分页的水平scrollView,并且在每个页面上都有一个UIWebView,可以处理我需要的垂直滚动。
汤姆·雷德曼

优秀!但是有人可以解释它是如何工作的(将contentSize height设置为1)!
Praveen

它确实有效,但是为什么以及如何起作用?有人能从中得出一些道理吗?
Marko Francekovic

27

对于像我这样的懒人:

设置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;
}

16

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

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

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 2011年

@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; }
cduck 2011年

没为我工作。在飞行中设置contentOffset似乎会破坏分页行为。我选择了Mattias Wadman的解决方案。
Ortwin Gentz 2014年

13

我可能在这里挖墓,但是今天我在尝试解决相同的问题时偶然发现了这个帖子。这是我的解决方案,似乎效果很好。

我想显示一屏内容,但允许用户上下左右滚动到其他4屏之一。我有一个UIScrollView,尺寸为320x480,contentSize为960x1440。内容偏移量从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);
    }
}

您可能会发现,当用户放开滚动视图时,此代码不能很好地“减速”-它将停止运行。如果您不希望减速,那么对您来说可能很好。
andygeers 2011年

4

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


2
对于任何其他在此问题上绊脚石的人:我认为此答案已在澄清该问题的编辑之前发布。这将使您只能水平滚动,而不能解决能够垂直或水平滚动但不能对角滚动的问题。
andygeers 2011年

3

这是我UIScrollView只允许正交滚动的页面的实现。确保设置pagingEnabledYES

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

1
在iOS 7上也很好用。谢谢!
Ortwin Gentz 2014年

3

我还必须解决这个问题,尽管安德烈·塔兰佐夫(Andrey Tarantsov)的答案肯定有很多有用的信息可帮助您理解UIScrollViews工作原理,但我认为该解决方案有些复杂。我的解决方案不涉及任何子类化或触摸转发,如下所示:

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

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

2

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

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

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

2

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

    }

}

希望这可以帮助。


1

从文档:

“默认值为NO,这表示允许在水平和垂直方向上滚动。如果该值为YES,并且用户开始在一个通用方向(水平或垂直)上拖动,则滚动视图将禁止在另一个方向上滚动。 ”

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

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


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];
    } 
}

对我来说就像个魅力!


1
不明白这对您有什么帮助...您可以上传示例项目吗?
hfossli 2010年



0

如果您尚未尝试在3.0 Beta中进行此操作,建议您试一试。我当前在滚动视图中使用一系列表视图,并且按预期工作。(无论如何,滚动确实可以。在寻找完全不同的问题的解决方法时发现了这个问题...)


0

对于那些在Swift中寻找@AndreyTarantsov第二个解决方案的人,其代码如下:

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

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

希望对新来者有用!


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

-3

如果您正在使用界面构建器来设计界面,则可以轻松完成。

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


1
IB中没有选项可以执行此操作。您可能要引用的两个选项与滚动条的显示有关,而不是实际的滚动。此外,方向锁定仅在滚动开始后锁定方向。
Sophtware 2010年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.