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

时间:2009-04-07 23:44:01

标签: ios objective-c uiscrollview scroll-paging

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

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

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

20 个答案:

答案 0 :(得分:113)

答案 1 :(得分:53)

每次都适合我......

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

答案 2 :(得分:25)

对于像我这样懒惰的人:

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

答案 3 :(得分: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;
    }
 }

答案 4 :(得分:13)

我可能会在这里唠叨,但是当我试图解决同样的问题时,我今天偶然发现了这篇文章。这是我的解决方案,似乎工作得很好。

我希望显示一系列内容,但允许用户滚动到其他4个屏幕中的一个,向上和向下。我有一个大小为320x480的UIScrollView和一个960x1440的contentSize。内容偏移量从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);
    }
}

答案 5 :(得分:4)

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

答案 6 :(得分:3)

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

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

答案 7 :(得分:3)

这是我实现的只有正交滚动的分页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

答案 8 :(得分: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
        }

    }

}

希望这有帮助。

答案 9 :(得分:2)

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

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

答案 10 :(得分: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;  
}           

答案 11 :(得分:1)

来自文档:

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

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

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

答案 12 :(得分:1)

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

答案 13 :(得分: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];
    } 
}

对我来说就像魅力一样!

答案 14 :(得分:1)

需要在_isHorizontalScrolltouchesEnded中将touchesCancelled重置为否。

答案 15 :(得分:0)

如果您还没有尝试在3.0测试版中执行此操作,我建议您试一试。我目前在滚动视图中使用一系列表视图,它按预期工作。 (好吧,无论如何,滚动都是这样。在寻找一个完全不同的问题时找到这个问题...)

答案 16 :(得分: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
        }
    }

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

希望这对新来的人有用!

答案 17 :(得分: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
    }
}

答案 18 :(得分:0)

这是 Benjamin's answer

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

答案 19 :(得分:-1)

如果您使用界面构建器来设计界面 - 这可以非常轻松地完成。

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

相关问题