UIScrollView Бесконечная прокрутка - PullRequest
16 голосов
/ 07 августа 2010

Я пытаюсь настроить просмотр с бесконечной (горизонтальной) прокруткой.

Прокрутка вперед проста - я реализовал scrollViewDidScroll, и когда contentOffset приближается к концу, я увеличиваю размер содержимого scrollview и добавляю больше данных в пространство (мне придется иметь дело с эффектом увечья, который произойдет позже! )

Моя проблема с прокруткой назад - план состоит в том, чтобы увидеть, когда я подхожу к началу представления прокрутки, затем, когда я увеличиваю размер содержимого, перемещаю существующий контент вперед, добавляю новые данные в начало и затем - важно настроить 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 </p>

Отлично! - но цифры должны продолжать уменьшаться в 4000, но следующие записи в журнале гласят: 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 и т.д ....

Любые идеи ????

Спасибо

Бен.

Ответы [ 10 ]

17 голосов
/ 11 мая 2012

Я нашел отличный пример приложения от Apple для реализации бесконечной прокрутки, используя аналогичную идею в ОП.Очень просто и самое главное без разрывов .

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

Они реализовали «повторное центрирование содержимого» каждый раз, когда layoutSubviews вызывалось в UIScrollView.

Единственная корректировка, которую я сделал, заключалась в том, чтобы перерабатывать «эффект мозаики» вместо того, чтобы выбрасывать старые плитки и выделять новые.

7 голосов
/ 07 августа 2010

Может случиться так, что если вы устанавливаете эти числа там, это не сильно впечатляет, когда вы устанавливаете contentOffset под его руками.Поэтому он просто продолжает устанавливать то, что, по его мнению, должно быть contentOffset на следующий момент - без проверки того, изменился ли contentOffset за это время.

Я бы создал подкласс UIScrollView и поместил магию в метод setContentOffset.По моему опыту, все изменения смещения контента проходят через этот метод, даже изменение смещения контента, вызванное внутренней прокруткой.Просто сделайте [super setContentOffset:..] в какой-то момент, чтобы передать сообщение реальному UIScrollView.

Может быть, если вы добавите туда свое переключающее действие, оно будет работать лучше.Вы могли бы по крайней мере определить настройку 3000-off для contentOffset и исправить ее перед передачей сообщения.Если бы вы также переопределили метод contentOffset, вы могли бы попытаться выяснить, можете ли вы создать виртуальный бесконечный размер контента и уменьшить его до реальных пропорций "под капотом".

6 голосов
/ 12 октября 2012

Когда я столкнулся с этой проблемой, у меня было 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];
    }
}
5 голосов
/ 24 апреля 2011

Я сделал пример проекта для решения вашей проблемы.

Вы можете скачать код с

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

это прокручивает в обоих направлениях бесконечно и загружает изображения на ходу.

5 голосов
/ 09 августа 2010

(надеюсь, я публикую это правильно - я новичок здесь??)

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

Примечания:

Если вы действительно хотите бесконечный цикл, вам нужно откорректировать числа - этот код замечает, когда вы приближаетесь к краю, и перемещает вас чуть дальше середины

Мой контентРазмер 6720 (21 страница)

Меня интересует только горизонтальная прокрутка, поэтому сохраняйте только значения x и жестко кодируйте y в 0!

Ben

1 голос
/ 04 февраля 2015

Я сделал подкласс UIScrollView, который просто делает то, что вы хотите. Он просто прокручивается навсегда в любых направлениях, даже в обоих направлениях одновременно. Он поддерживает плавную прокрутку и прокрутку подкачки

https://github.com/vasvf/NPInfiniteScrollView

1 голос
/ 30 ноября 2011

Я разработал этот вид uiscrollview, так что вы можете проверить мой проект на Infinite UIScrollView в обоих направлениях

0 голосов
/ 15 ноября 2013

Таким образом, одна проблема заключается в том, что установка 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 голосов
/ 18 октября 2013

Вы можете увидеть этот пример

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 голосов
/ 22 апреля 2011

Посмотрите на это тоже (видео даст вам быстрое представление о том, что оно делает): http://dev.doukasd.com/2011/04/infinite-scrolling-dial-control-for-ios/

Это не горизонтально, но это должно быть полезно. Я попробовал то, что вы предлагали, но установка смещения контента никогда не выглядела правильно, пока прокручивалось представление прокрутки, анимация всегда ломалась.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...