当前位置:  开发笔记 > IOS > 正文

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

如何解决《UIScrollView:水平分页,垂直滚动?》经验,为你挑选了5个好方法。

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

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

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



1> Andrey Taran..:

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

请注意,您需要使用UIScrollView pagingEnabled=YES来切换页面,但需要pagingEnabled=NO垂直滚动.

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

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

因此策略是让外部滚动视图仅处理水平滚动,而内部滚动视图仅处理垂直滚动.要实现这一点,您必须知道UIScrollView如何在内部工作.它会覆盖hitTest方法并始终返回自身,以便所有触摸事件都进入UIScrollView.然后里面touchesBegan,touchesMoved等它检查是否是感兴趣的事件,并且会处理或将其传递到内部组件.

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

如果您未在150毫秒内显着移动手指,则会将事件传递到内部视图.

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

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

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

请注意当您触摸桌子时,稍微握住手指然后开始滚动,您首先突出显示您触摸的行,但之后不再突出显示.

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

如果delaysContentTouches为NO,则不使用计时器 - 事件立即进入内部控制(但如果您将手指移得足够远则取消)

如果cancelsTouches为NO,则一旦将事件发送到控件,滚动将永远不会发生.

需要注意的是的UIScrollView接收所有 touchesBegin,touchesMoved,touchesEndedtouchesCanceled从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为NO,这样就不会有额外的惊喜计时器干扰.

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

鉴于你总是前进touchesBegan,你也必须前进touchesCancelledtouchesEnded.你必须把touchesEndedtouchesCancelled,但是,因为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

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

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

第二种策略:如果由于某种原因嵌套的UIScrollViews无法解决或证明难以正确使用,您可以尝试与一个UIScrollView相处,pagingEnabled从您的scrollViewDidScroll委托方法动态切换其属性.

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

如果不工作,要么或导致马虎的视觉效果,你必须重写touchesBegan,touchesMoved等等,而不是向对角线移动事件的UIScrollView.(在这种情况下,用户体验不是最理想的,因为你必须忽略对角线的移动,而不是强迫它们朝一个方向.如果你真的有冒险精神,你可以编写自己的UITouch,就像RevengeTouch一样. -C是普通的老C,世界上没有什么比C更好的了;只要没有人检查对象的真实类,我认为没有人做,你可以使任何类看起来像任何其他类.这打开了可以使用您想要的任何坐标来合成您想要的任何触摸.)

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

好的,这篇小文章有点太长了.几天前我在ScrollingMadness上工作后,我还在进入UIScrollView游戏.

PS如果你有任何这些工作并且想分享,请发送电子邮件给我在andreyvit@gmail.com的相关代码,我很乐意将它包含在我的ScrollingMadness包中.

PPS将这篇小文章添加到ScrollingMadness自述文件中.


请注意,这个嵌套的scrollview行为现在在SDK中开箱即用,不需要有趣的业务

2> 小智..:

这对我每次都有用......

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


对于任何绊倒这个问题的人:我认为这个答案是在编辑之前发布的,澄清了这个问题.这将允许您*仅*水平滚动,它不解决能够垂直或水平滚动但不能对角滚动的问题.

3> 小智..:

对于像我这样懒惰的人:

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



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)

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

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



5> James J..:

我可能会在这里进行重击,但今天我偶然发现了这篇文章,因为我试图解决同样的问题.这是我的解决方案,似乎工作得很好.

我想显示一些屏幕内容,但允许用户滚动到其他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);
    }
}

推荐阅读
N个小灰流_701
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有