UIScrollView Infinite Scrolling

前端 未结 10 1821
伪装坚强ぢ
伪装坚强ぢ 2020-11-27 13:45

I\'m attempting to setup a scrollview with infinite (horizontal) scrolling.

Scrolling forward is easy - I have implemented scrollViewDidScroll, and when the contentO

相关标签:
10条回答
  • 2020-11-27 13:50

    When I had faced this problem I had 3 images, which needed to be able to scroll infinitely in either direction. The optimal solution would probably be to load 3 images and modifying the content panel when user moves to rightmost/ leftmost image but I have found an easier solution. (Did some editing on the code, might contain typos)

    Instead of 3 images, I have setup a scroll view with 5 images, like:

    3 | 1 | 2 | 3 | 1
    

    and calculated the content view accordingly, whereas the content size is fixed. Here is the code:

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

    Now that the images are added, the only thing is to create the illusion of infinite scrolling. To do that I have "teleported" the user into the three main image, whenever he tries to scroll to one of the outer two images. It is important to do that not-animated, so that the user won't be able to feel it:

    - (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 讨论(0)
  • 2020-11-27 13:53

    I have made a sample project to solve your problem.

    you can download the code from

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

    this scrolls in both directions endlessly and loads images on the go.

    0 讨论(0)
  • 2020-11-27 13:56

    I've made a subclass of UIScrollView that just does what you want. It just scrolls forever in any directions, even in both directions simultaneously. It supports smooth scrolling and paging scrolling

    https://github.com/vasvf/NPInfiniteScrollView

    0 讨论(0)
  • 2020-11-27 13:58

    I have developed this kind of uiscrollview, so you can check my project at Infinite UIScrollView in both direction

    0 讨论(0)
  • 2020-11-27 14:02

    So one issue is that setting the contentOffset while in the scrollViewDidScroll: delegate method will cause the delegate method to fire again while you are inside it. So what I do is remove the delegate > setContentOffset > set the delegate again.

    Here's my code:

    #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 讨论(0)
  • 2020-11-27 14:06

    It could be that whatever is setting those numbers in there, is not greatly impressed by you setting the contentOffset under its hands. So it just goes on setting what it thinks should be the contentOffset for the next instant - without verifying if the contentOffset has changed in the meantime.

    I would subclass UIScrollView and put the magic in the setContentOffset method. In my experience all content-offset changing passes through that method, even the content-offset changing induced by the internal scrolling. Just do [super setContentOffset:..] at some point to pass the message on to the real UIScrollView.

    Maybe if you put your shifting action in there it will work better. You could at least detect the 3000-off setting of contentOffset, and fix it before passing the message on. If you would also override the contentOffset method, you could try and see if you can make a virtual infinite content size, and reduce that to real proportions "under the hood".

    0 讨论(0)
提交回复
热议问题