UIScrollView Бесконечная прокрутка
Я пытаюсь настроить прокрутку с бесконечной (горизонтальной) прокруткой.
Прокрутка вперед проста - я реализовал scrollViewDidScroll, и когда contentOffset приближается к концу, я делаю содержимое прокрутки более крупным и добавляю больше данных в пространство (мне придется иметь дело с эффектом вреда, который будет позже! )
Моя проблема - прокрутка назад - план заключается в том, чтобы увидеть, когда я приближаюсь к началу прокрутки, а затем, когда я делаю контент больше, перемещаю существующий контент, добавляем новые данные в начало, а затем - важно настроить 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
Отлично! - но цифры должны продолжать снижаться в 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
и т.д....
Любые идеи????
Спасибо
Бен.
Ответы
Ответ 1
Возможно, что все, что устанавливает эти числа там, не сильно впечатлено тем, что вы устанавливаете contentOffset под свои руки. Поэтому он просто настраивает то, что, по его мнению, должен быть contentOffset в течение следующего момента - без проверки того, изменилось ли contentOffset тем временем.
Я бы подклассифицировал UIScrollView и поместил магию в метод setContentOffset
. По моему опыту все изменения смещения содержимого проходят через этот метод, даже изменение смещения содержимого, вызванное внутренней прокруткой. Просто сделайте [super setContentOffset:..]
в какой-то момент, чтобы передать сообщение реальному UIScrollView.
Возможно, если вы поместите свое смещение в нем, оно будет работать лучше. Вы могли бы, по крайней мере, определить 3000-off настройку contentOffset и исправить его до передачи сообщения. Если вы также переопределите метод contentOffset
, вы можете попробовать и посмотреть, можете ли вы сделать виртуальный размер бесконечного содержимого и уменьшить его до реальных пропорций "под капотом".
Ответ 2
Я нашел действительно отличное приложение Apple для реализации Infinite Scrolling, используя аналогичную идею в OP. Очень просто и, самое главное, без разрывов.
http://developer.apple.com/library/ios/#samplecode/StreetScroller/Introduction/Intro.html
Они реализовали "повторное размещение содержимого" каждый раз, когда layoutSubviews
вызывался в UIScrollView.
Единственное, что я сделал, это переработать "эффект черепицы" вместо того, чтобы отбрасывать старые плитки и выделять новые.
Ответ 3
Когда я столкнулся с этой проблемой, у меня было 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];
}
}
Ответ 4
(надеюсь, что опубликую это правильно - я здесь новый!?)
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
Примечания:
Если вам действительно нужен бесконечный цикл, вам нужно будет отрегулировать числа - эти коды замечают, когда вы приближаетесь к краю и перемещаете вас только за середину
My contentSize - 6720 (21 страница)
Мне интересны только прокрутки по горизонтали, поэтому сохраняйте только значения x и жесткий код y до 0!
Бен
Ответ 5
Я создал образец проекта для решения вашей проблемы.
вы можете загрузить код из
https://code.google.com/p/iphone-infinite-horizontal-scroller/
это прокручивается в обоих направлениях бесконечно и загружает изображения на ходу.
Ответ 6
Я разработал этот вид uiscrollview, поэтому вы можете проверить мой проект на Бесконечный UIScrollView в обоих направлениях
Ответ 7
Я создал подкласс UIScrollView, который просто делает то, что вы хотите. Он просто прокручивается вечно в любых направлениях, даже в обоих направлениях одновременно.
Он поддерживает плавную прокрутку и прокрутку страниц
https://github.com/vasvf/NPInfiniteScrollView
Ответ 8
Посмотрите на это тоже (видео даст вам краткое представление о том, что он делает): http://dev.doukasd.com/2011/04/infinite-scrolling-dial-control-for-ios/
Он не горизонтальный, но он должен быть полезен. Я попробовал то, что вы предлагали, но настройка смещения содержимого никогда не выглядела правильно, пока анимация анимации прокручивалась, анимация всегда будет разбиваться.
Ответ 9
Вы можете увидеть этот пример
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)];
}
}
Ответ 10
Таким образом, одна из проблем заключается в том, что установка contentOffset в то время как в методе scrollViewDidScroll: delegate приведет к тому, что метод делегата будет снова запущен, пока вы находитесь внутри него. Так что я делаю удаление делегатa > 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