【问题标题】:UIScrollView Infinite ScrollingUIScrollView 无限滚动
【发布时间】:2011-03-26 16:59:30
【问题描述】:

我正在尝试设置一个无限(水平)滚动的滚动视图。

向前滚动很容易——我已经实现了 scrollViewDidScroll,当 contentOffset 接近尾声时,我将滚动视图的 contentsize 变大并在空间中添加更多数据(我将不得不处理稍后会产生的严重影响! )

我的问题是向后滚动 - 计划是看看我何时接近滚动视图的开头,然后当我确实使 contentsize 更大时,移动现有内容,将新数据添加到开头,然后 -重要的是调整 contentOffset 使视口下的数据保持不变。

如果我慢慢滚动(或启用分页),这非常有效,但如果我走得快(甚至不是很快!)它会发疯!代码如下:

- (void) scrollViewDidScroll:(UIScrollView *)scrollView {

    float pageNumber = scrollView.contentOffset.x / 320;
    float pageCount = scrollView.contentSize.width / 320;

    if (pageNumber > pageCount-4) {
        //Add 10 new pages to end
        mainScrollView.contentSize = CGSizeMake(mainScrollView.contentSize.width + 3200, mainScrollView.contentSize.height);
        //add new data here at (320*pageCount, 0);
    }

    //*** the problem is here - I use updatingScrollingContent to make sure its only called once (for accurate testing!)
    if (pageNumber < 4 && !updatingScrollingContent) {

        updatingScrollingContent = YES;

        mainScrollView.contentSize = CGSizeMake(mainScrollView.contentSize.width + 3200, mainScrollView.contentSize.height);
        mainScrollView.contentOffset = CGPointMake(mainScrollView.contentOffset.x + 3200, 0);
        for (UIView *view in [mainContainerView subviews]) {
            view.frame = CGRectMake(view.frame.origin.x+3200, view.frame.origin.y, view.frame.size.width, view.frame.size.height);
        }
        //add new data here at (0, 0);      
    }

    //** MY CHECK!
    NSLog(@"%f", mainScrollView.contentOffset.x);
}

随着滚动的发生,日志显示: 1286.500000 1285.500000 1284.500000 1283.500000 1282.500000 1281.500000 1280.500000

然后,当 pageNumber

太棒了! - 但数字应该会在 4,000 秒内继续下降,但下一个日志条目显示: 1278.000000 1277.000000 1276.500000 1275.500000 等等……

从中断的地方继续!

仅作记录,如果滚动缓慢,日志会显示: 1294.500000 1290.000000 1284.500000 1280.500000 4476.000000 4476.000000 4473.000000 4470.000000 4467.500000 4464.000000 4460.500000 4457.500000 等等……

有什么想法吗????

谢谢

本。

【问题讨论】:

    标签: iphone uiscrollview


    【解决方案1】:

    当我遇到这个问题时,我有 3 张图片,它们需要能够在任一方向上无限滚动。最佳解决方案可能是在用户移动到最右边/最左边的图像时加载 3 个图像并修改内容面板,但我找到了一个更简单的解决方案。 (对代码做了一些编辑,可能有错别字)

    我设置了一个包含 5 张图片的滚动视图,而不是 3 张图片,例如:

    3 | 1 | 2 | 3 | 1
    

    并相应地计算内容视图,而内容大小是固定的。代码如下:

    for ( NSUInteger i = 1; i <= NO_OF_IMAGES_IN_SCROLLVIEW + 2 ; i ++) {
    
        UIImage *image;
        if ( i  % 3 == 1){
    
            image = [UIImage imageNamed:[NSString stringWithFormat:@"img1.png"]];
        }
    
        else if (i % 3 == 2 ) {
    
            image = [UIImage imageNamed:[NSString stringWithFormat:@"img2.png"]];
    
        }
    
        else {
    
            image = [UIImage imageNamed:[NSString stringWithFormat:@"img3.png"]];
        }
    
        UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake((i-1)*_scrollView.frame.size.width, 0, _scrollView.frame.size.width, _scrollView.frame.size.height)];
        imageView.contentMode=UIViewContentModeScaleToFill;
        [imageView setImage:image];
        imageView.tag=i+1;
        [self.scrollView addSubview:imageView];
    }
    
    [self.scrollView setContentOffset:CGPointMake(self.scrollView.frame.size.width, 0)];
    [scrMain setContentSize:CGSizeMake(self.scrollView.frame.size.width * ( NO_OF_IMAGES_IN_SCROLLVIEW + 2 ), self.scrollView.frame.size.height)];
    

    现在添加了图像,唯一的事情就是创造无限滚动的错觉。为此,每当用户尝试滚动到外部两个图像之一时,我都会将用户“传送”到三个主图像中。重要的是不要动画,这样用户就不会感觉到它:

    - (void)scrollViewDidScroll:(UIScrollView *)scrollView {
    
        if (scrollView.contentOffset.x < scrollView.frame.size.width ){
    
            [scrollView scrollRectToVisible:CGRectMake(scrollView.contentOffset.x + 3 * scrollView.frame.size.width, 0, scrollView.frame.size.width, scrollView.frame.size.height) animated:NO];
        }
    }
    
        else if ( scrollView.contentOffset.x > 4 * scrollView.frame.size.width  ){
    
            [scrollView scrollRectToVisible:CGRectMake(scrollView.contentOffset.x - 3 * scrollView.frame.size.width, 0, scrollView.frame.size.width, scrollView.frame.size.height) animated:NO];
        }
    }
    

    【讨论】:

      【解决方案2】:

      我已经创建了一个 UIScrollView 的子类,它可以满足您的需求。它只会在任何方向上永远滚动,甚至同时在两个方向上滚动。 支持平滑滚动和分页滚动

      https://github.com/vasvf/NPInfiniteScrollView

      【讨论】:

      • 隐藏了scrollView的子视图,占用大量内存。
      【解决方案3】:

      所以一个问题是在 scrollViewDidScroll: 委托方法中设置 contentOffset 会导致委托方法在您在其中时再次触发。所以我要做的是删除委托 > setContentOffset > 再次设置委托。

      这是我的代码:

      #import "ViewController.h"
      
      @interface ViewController () <UIScrollViewDelegate>
      @property (weak, nonatomic) IBOutlet UIScrollView *scrollView;
      @property (strong, nonatomic) NSMutableArray *labels;
      @property (weak, nonatomic) UILabel *originLabel;
      
      - (void)addScrollViewLabels;
      - (void)adjustOrigins:(float)deltaX;
      
      @end
      
      
      @implementation ViewController
      
      - (void)viewDidLoad
      {
          [super viewDidLoad];
      
          // just some starting size
          CGSize size = self.scrollView.frame.size;
          CGSize contentSize = CGSizeMake(size.width * 4, size.height);
          [self.scrollView setContentSize:contentSize];
      
          // just some starting offset
          CGPoint contentOffset = CGPointMake((contentSize.width / 2), 0);
          [self.scrollView setContentOffset:contentOffset];
      
          [self addScrollViewLabels];
      }
      
      - (void)scrollViewDidScroll:(UIScrollView *)scrollView {
          CGSize contentSize = scrollView.contentSize;
          CGSize size = scrollView.frame.size;
          CGPoint contentOffset = scrollView.contentOffset;
      
          const float kContentOffsetBuffer = 100;
          const float kContentSizeGrowth = (4 * size.width);
      
          BOOL shouldGrowContentSize = (contentOffset.x > (contentSize.width - size.width - kContentOffsetBuffer))
                                      || (contentOffset.x < (kContentOffsetBuffer));
          if (shouldGrowContentSize) {
              // the contentOffset has reached a point where we need to grow the contentSize
              CGSize adjustedContentSize = CGSizeMake(contentSize.width + kContentSizeGrowth, contentSize.height);
              [self.scrollView setContentSize:adjustedContentSize];
      
              if(contentOffset.x < (kContentOffsetBuffer)) {
                  // the growth needs to happen on the left side which means that we need to adjust the contentOffset to compensate for the growth.
                  // this is not necessary when growth happens to the right since the contentOffset is the same.
                  CGPoint adjustedContentOffset = CGPointMake(contentOffset.x + kContentSizeGrowth, contentOffset.y);
                  [self.scrollView setDelegate:nil];
                  [self.scrollView setContentOffset:adjustedContentOffset];
                  [self.scrollView setDelegate:self];
                  [self adjustOrigins:kContentSizeGrowth];
              }
      
              [self addScrollViewLabels];
          }
      }
      
      
      - (void)addScrollViewLabels {
          const float kOriginY = 100;
      
          if (!self.labels) {
              self.labels = [NSMutableArray array];
              float originX = [self.scrollView contentOffset].x;
              UILabel *label0 = [[UILabel alloc] initWithFrame:CGRectMake(originX, kOriginY, 100, 30)];
              label0.text = @"0";
              [self.scrollView addSubview:label0];
              [self.labels addObject:label0];
              self.originLabel = label0;
          }
      
          CGSize contentSize = [self.scrollView contentSize];
          const float kIncrementAmount = 75;
          NSInteger indexOfOriginLabel = [self.labels indexOfObject:self.originLabel];
      
          // add labels to the right
          UILabel *lastLabel = [self.labels lastObject];
          float lastOriginX = lastLabel.frame.origin.x;
          for (float x = (lastOriginX + kIncrementAmount); (x < (contentSize.width)) ; x += kIncrementAmount) {
              UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(x, kOriginY, 100, 30)];
              NSInteger indexFromOrigin = ([self.labels count] - indexOfOriginLabel);
              label.text = [@(indexFromOrigin) description];
              [self.scrollView addSubview:label];
              [self.labels addObject:label];
              [label setNeedsDisplay];
          }
      
          // add labels to the left
          UILabel *firstLabel = [self.labels firstObject];
          float firstOriginX = firstLabel.frame.origin.x;
          for (float x = (firstOriginX - kIncrementAmount); (x >= 0) ; x -= kIncrementAmount) {
              UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(x, kOriginY, 100, 30)];
              NSInteger indexFromOrigin = -(indexOfOriginLabel + 1);
              label.text = [@(indexFromOrigin) description];
              [self.scrollView addSubview:label];
              [self.labels insertObject:label atIndex:0];
              indexOfOriginLabel++;
              [label setNeedsDisplay];
          }
      }
      
      - (void)adjustOrigins:(float)deltaX {
          for (UILabel *label in self.labels) {
              CGRect frame = label.frame;
              frame.origin.x += deltaX;
              [label setFrame:frame];
              [label setNeedsDisplay];
          }
      }
      
      
      @end
      

      【讨论】:

      • 出于我的目的,Apple 的 StreetScroller 演示是不行的,因为我需要能够看到滚动视图指示器。即使在 contentSize 增长时,这种方法也可以让您看到它们。
      【解决方案4】:

      你可以看看这个例子

      numbercolors=[[NSMutableArray alloc] init];
      
      //total count of array is  49 
      
       numbercolors = [NSMutableArray arrayWithObjects:@"25",@"26",@"27",@"28",@"29",@"31",@"32",@"33",@"34",@"35","0",@"1",@"2",@"3",@"4",@"5",@"6",@"7",@"8",@"9",@"10",@"11",@"12",@"13",@"14",@"15",@"16",@"17",@"18",@"19",@"20",@"21",@"22",@"23",@"24",@"25",@"26",@"27",@"28",@"29",@"30",@"31",@"32",@"33",@"34",@"35", @"0",@"1",@"2",@"3",nil];
      
        int x=2500;
      
       for (NSInteger index = 0; index < [numbercolors count]; index++)
       {
        UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
      
        button.frame = CGRectMake(x ,0,29.0,77.0);
      
        button.tag = index;
      
        [button setTitle:[numbercolors objectAtIndex:index] forState:UIControlStateNormal];
      
        [button addTarget:self action:@selector(didTapButton:) 
      
        forControlEvents:UIControlEventTouchUpInside];
      
        [coloringScroll addSubview:button];
      
         x=x+70+29;
       } 
        [coloringScroll setContentSize:CGSizeMake(5000+ (29+70)*[numbercolors count], 1)];
      
        [coloringScroll setContentOffset:CGPointMake(2500+(29+70)*11, 0)];
      
      
      - (void)scrollViewDidScroll:(UIScrollView *)scrollView
      
      {
      
      if (scrollView.contentOffset.x > 2500+(29+70)*4 + ((29+70)*36))
      {
      
       [scrollView setContentOffset:CGPointMake(scrollView.contentOffset.x-((29+70)*36),  0)];
      
      }
      
       if (scrollView.contentOffset.x < 2500+(29+70)*4)
      
      {
       [scrollView setContentOffset:CGPointMake(scrollView.contentOffset.x+((29+70)*36), 
      
       0)];
      }
      
      }
      

      【讨论】:

        【解决方案5】:

        我发现 Apple 提供了一个非常棒的示例应用程序,它使用 OP 中的类似想法来实现无限滚动。非常简单,最重要的是不会撕裂。

        http://developer.apple.com/library/ios/#samplecode/StreetScroller/Introduction/Intro.html

        每次在 UIScrollView 上调用 layoutSubviews 时,他们都会实现“内容重新定位”。

        我所做的唯一调整是回收“平铺效果”,而不是扔掉旧瓷砖并分配新瓷砖。

        【讨论】:

        • 他们如何在不停止加速的情况下重新定位内容?
        • @yourfriendzak 他们所做的只是在滚动偏移到达某个边界后重新定位,因此在此过程中加速度值不变。
        【解决方案6】:

        我已经开发了这种uiscrollview,所以你可以在Infinite UIScrollView in both direction查看我的项目

        【讨论】:

          【解决方案7】:

          我已经制作了一个示例项目来解决您的问题。

          您可以从以下网址下载代码

          https://code.google.com/p/iphone-infinite-horizontal-scroller/

          这会在两个方向无限滚动并在旅途中加载图像。

          【讨论】:

          • 你能把这个放到 Github 上吗?
          【解决方案8】:

          也看看这个(视频会让你快速了解它的作用):http://dev.doukasd.com/2011/04/infinite-scrolling-dial-control-for-ios/

          它不是水平的,但应该很有用。我尝试了您的建议,但是在滚动视图动画时设置内容偏移量看起来不正确,动画总是会中断。

          【讨论】:

            【解决方案9】:

            (希望我发布的正确 - 我是新来的!?)

            mvds 出现了 - 谢谢 - UIScrollView 的子类化效果很好 - 我还没有实现新数据的移动和加载,但我有一个 UIScrollView 可以无限循环滚动!

            代码如下:

            #import "BRScrollView.h"
            
            @implementation BRScrollView
            
            - (id)awakeFromNib:(NSCoder *)decoder {
                offsetAdjustment = 0;
                [super initWithCoder:decoder];
                return self;
            }
            
            - (void)setContentOffset:(CGPoint)contentOffset {
            
                float realOffset = contentOffset.x + offsetAdjustment;
            
                //This happens when contentOffset has updated correctly - there is no need for the adjustment any more
                if (realOffset < expectedContentOffset-2000 || realOffset > expectedContentOffset+2000) {
                    offsetAdjustment = 0;
                    realOffset = contentOffset.x;
                }
            
                float pageNumber = realOffset / 320;
                float pageCount = self.contentSize.width / 320;
            
                if (pageNumber > pageCount-4) {
                    offsetAdjustment -= 3200;
                    realOffset -= 3200;
                }
            
                if (pageNumber < 4) {
                    offsetAdjustment += 3200;
                    realOffset += 3200; 
                }
            
                //Save expected offset for next call, and pass the real offset on
                expectedContentOffset = realOffset;     
                [super setContentOffset:CGPointMake(realOffset, 0)];
            
            
            }
            
            - (void)dealloc {
                [super dealloc];
            }
            
            
            @end
            

            注意事项:

            如果你真的想要一个无限循环,你需要调整数字 - 当你接近边缘时,这段代码会注意到并将你移动到刚刚超出中间的位置

            我的 contentSize 是 6720(21 页)

            我只对水平滚动感兴趣,所以只保存 x 值并将 y 硬编码为 0!

            本

            【讨论】:

              【解决方案10】:

              可能是无论在那里设置这些数字,您将 contentOffset 设置在它的手下并没有给人留下深刻的印象。所以它只是继续设置它认为应该是下一个瞬间的 contentOffset - 而不验证 contentOffset 在此期间是否发生了变化。

              我会将 UIScrollView 子类化并将魔法放在setContentOffset 方法中。以我的经验,所有内容偏移更改都通过该方法,即使是由内部滚动引起的内容偏移更改。只需在某个时候执行[super setContentOffset:..] 即可将消息传递给真正的 UIScrollView。

              也许如果你把你的换档动作放在那里它会更好。您至少可以检测到 contentOffset 的 3000-off 设置,并在传递消息之前对其进行修复。如果您还要覆盖 contentOffset 方法,您可以尝试看看是否可以制作虚拟无限内容大小,然后“在后台”将其缩小到真实比例。

              【讨论】:

              • 小心,似乎setContentOffset:animated: 取消当前滚动,但不是setContentOffset:... 在UIScrollView 的后代中覆盖setContentOffset: 并调整偏移工作(至少在iOS SDK 4.3 上) )。
              • 我注意到 setContentOffset:animated: 和 setContentOffset: 都不幸取消了滚动动画(iPad2 SDK 4.3)。
              猜你喜欢
              • 1970-01-01
              • 2012-03-15
              • 1970-01-01
              • 1970-01-01
              • 2012-10-04
              • 1970-01-01
              • 2012-05-10
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多