2010-08-07 133 views
16

我试图设置无限(水平)滚动滚动视图。滚动向前很容易 - 我已经实现了scrollViewDidScroll,并且当contentOffset接近结束时,我将scrollview内容做得更大,并将更多数据添加到空间中(我将不得不处理稍后会有的残缺效果) !)UIScrollView无限滚动

我的问题是滚动回来 - 计划是看我什么时候接近滚动视图的开始,然后当我确定内容更大时,将现有内容一起移动,将新数据添加到开始处然后 - 重要的是调整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(我们正在接近获得开头): 4479.500000 4479.500000

Gr吃! - 但数字应该继续往下走的4,000秒,但接下来的日志条目阅读: 1278.000000 1277.000000 1276.500000 1275.500000 等....

从那里离开Continiuing!

只是为了记录在案,如果慢慢地滚动日志上写着: 1294.500000 1290.000000 1284.500000 1280.500000 4476.000000 4476.000000 4473.000000 4470.000000 4467.500000 4464.000000 4460.500000 4457.500000 等....

有任何想法吗????

谢谢

本。

回答

7

可能是因为无论在那里设置这些数字,都不会在您将contentOffset设置在其手中时留下深刻的印象。因此,它只是继续设置它认为应该是下一个时刻的contentOffset,而不验证contentOffset是否在此期间发生了变化。

我会继承UIScrollView并将魔法放在setContentOffset方法中。根据我的经验,所有内容偏移量变化都会通过该方法,即使内部滚动引起的内容偏移量变化也是如此。在某些时候只需执行[super setContentOffset:..]即可将消息传递给真正的UIScrollView。

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

+1

小心,似乎'setContentOffset:动画:'取消当前的滚动,而不是'setContentOffset:'...'重写setContentOffset:在''的后裔UIScrollView'并调整偏移量(至少在iOS SDK 4.3上)。 – 2011-06-30 08:33:36

+0

我注意到setContentOffset:animated:和setContentOffset:不幸地取消了滚动动画(iPad2 SDK 4.3)。 – 2011-10-03 12:28:23

5

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

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!

5

我做了一个示例项目来解决您的问题。

你可以从

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

在旅途中不断地双向和装载图像下载代码这个卷轴。

+0

你可以把它放在Github上吗? – zakdances 2013-03-23 01:50:18

+1

https://github.com/cuterajat26/IphoneHorizo​​ntalScrollView/ – 2013-04-20 11:08:38

16

检查项目中,我发现了一个真正伟大的示例应用程序被苹果使用在OP类似的想法来实现无限滚动。非常简单,最重要的是不撕裂

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

他们实施了 “内容recentering” 每layoutSubviews被称为在UIScrollView的时间。

我做的唯一调整是回收“平铺效果”,而不是丢弃旧瓷砖和分配新瓷砖。

+0

他们如何在不停止加速的情况下更新内容? – zakdances 2013-03-23 01:49:44

+0

@yourfriendzak他们所做的一切是在它到达特定边界时重新调整滚动偏移量,因此加速度值在此过程中保持不变。 – rwyland 2013-03-23 16:51:21

6

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

代替3个图像,我有安装用5个图像的滚动视图,如:

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

你可以看到这个例子

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

} 
0

所以一个问题是,设置contentOffset而在scrollViewDidScroll:委托方法将导致委托方法,当你在里面时再次触发。所以我所做的是删除委托> 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 
+0

为了我的目的,Apple的StreetScroller演示是不行的,因为我需要能够看到滚动视图指示器。即使contentSize增长,这种方法也可以让你看到它们。 – 2013-11-15 19:00:25

1

我做的UIScrollView的子类,只是你想要做什么。它只是在任何方向永远滚动,即使在两个方向同时。 它支持平滑滚动和翻页滚动

https://github.com/vasvf/NPInfiniteScrollView

+0

它隐藏了scrollView的子视图并占用大量内存。 – TheTiger 2016-10-02 03:26:08