• Open Menu Close Menu
  • Apple
  • Shopping Bag
  • Apple
  • Mac
  • iPad
  • iPhone
  • Watch
  • TV
  • Music
  • Support
  • Search apple.com
  • Shopping Bag

Lists

Open Menu Close Menu
  • Terms and Conditions
  • Lists hosted on this site
  • Email the Postmaster
  • Tips for posting to public mailing lists
Infinite Scroll View Revisited
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Infinite Scroll View Revisited


  • Subject: Infinite Scroll View Revisited
  • From: Dave <email@hidden>
  • Date: Wed, 16 Oct 2013 18:42:16 +0100

Hi,

This has been bugging me for a while and today, I've managed to grab some time in order to try and get it working.

I based my class on the "Street Scroller" Sample App from Apple. The main methods that are important in "Street Scroller" are:


- (void)recenterIfNecessary
{
    CGPoint currentOffset = [self contentOffset];
    CGFloat contentWidth = [self contentSize].width;
    CGFloat centerOffsetX = (contentWidth - [self bounds].size.width) / 2.0;
    CGFloat distanceFromCenter = fabs(currentOffset.x - centerOffsetX);

    if (distanceFromCenter > (contentWidth / 4.0))
    {
        self.contentOffset = CGPointMake(centerOffsetX, currentOffset.y);

        // move content by the same amount so it appears to stay still
        for (UILabel *label in self.visibleLabels) {
            CGPoint center = [self.labelContainerView convertPoint:label.center toView:self];
            center.x += (centerOffsetX - currentOffset.x);
            label.center = [self convertPoint:center toView:self.labelContainerView];
        }
    }
}


- (void)tileLabelsFromMinX:(CGFloat)minimumVisibleX toMaxX:(CGFloat)maximumVisibleX
{
    // the upcoming tiling logic depends on there already being at least one label in the visibleLabels array, so
    // to kick off the tiling we need to make sure there's at least one label
    if ([self.visibleLabels count] == 0)
    {
        [self placeNewLabelOnRight:minimumVisibleX];
    }

    // add labels that are missing on right side
    UILabel *lastLabel = [self.visibleLabels lastObject];
    CGFloat rightEdge = CGRectGetMaxX([lastLabel frame]);
    while (rightEdge < maximumVisibleX)
    {
        rightEdge = [self placeNewLabelOnRight:rightEdge];
    }

    // add labels that are missing on left side
    UILabel *firstLabel = self.visibleLabels[0];
    CGFloat leftEdge = CGRectGetMinX([firstLabel frame]);
    while (leftEdge > minimumVisibleX)
    {
        leftEdge = [self placeNewLabelOnLeft:leftEdge];
    }

    // remove labels that have fallen off right edge
    lastLabel = [self.visibleLabels lastObject];
    while ([lastLabel frame].origin.x > maximumVisibleX)
    {
        [lastLabel removeFromSuperview];
        [self.visibleLabels removeLastObject];
        lastLabel = [self.visibleLabels lastObject];
    }

    // remove labels that have fallen off left edge
    firstLabel = self.visibleLabels[0];
    while (CGRectGetMaxX([firstLabel frame]) < minimumVisibleX)
    {
        [firstLabel removeFromSuperview];
        [self.visibleLabels removeObjectAtIndex:0];
        firstLabel = self.visibleLabels[0];
    }
}

- (void)layoutSubviews
{
    [super layoutSubviews];

    [self recenterIfNecessary];

    // tile content in visible bounds
    CGRect visibleBounds = [self convertRect:[self bounds] toView:self.labelContainerView];
    CGFloat minimumVisibleX = CGRectGetMinX(visibleBounds);
    CGFloat maximumVisibleX = CGRectGetMaxX(visibleBounds);

    [self tileLabelsFromMinX:minimumVisibleX toMaxX:maximumVisibleX];
}


I'm got to the stage where as far as I can see "recenterIfNecessary" just doesn't work correctly and I'm having difficulty trying to figure out what is actually supposed to do?

I am trying to scroll infinitely through the following images (these are Test Images so I can tell what is going on!), in the real app, these images will be downloaded. The height is fixed at 200 but the Width is Variable.

Index	File Name		ImageSize		XMin/XMax

0	Image01.png		200,200			0000,0199
1	Image02.png		200,200			0200,0399
2	Image03.png		410,200			0400,0809
3	Image04.png		410,200			0810,1219
4	Image05.png		200,200			1220,1419
5	Image06.png		410,200			1420,1829
6	Image07.png		200,200			1830,2029
7	Image08.png		200,200			2030,2229
8	Image09.png		200,200			2230,2429

0/9	Image01.png		200,200			2430,2629		Wrap Around back to 0.


Total Width of all Images:	2430

In my Class, if I comment out the  "recenterIfNecessary" call, all my Views get added correctly and are shown in the correct order, and I can scroll them to the end and it stops as expected.

With "recenterIfNecessary" enabled,  as soon you begin the scroll and it runs Frame Recalculation Loop, the Views go all over the place. So, I'm pretty sure that this method is wrong (or at least my implementation of it is wrong, when you take the rest of the code with it).

This the a dump of the Frame Rect for each view before and after the center has been set.


Before myFramgeRect: {{0, 0}, {200, 200}}
After myFramgeRect: {{703, 0}, {200, 200}}
Before myFramgeRect: {{200, 0}, {200, 200}}
After myFramgeRect: {{903, 0}, {200, 200}}
Before myFramgeRect: {{400, 0}, {410, 200}}
After myFramgeRect: {{1103, 0}, {410, 200}}
Before myFramgeRect: {{810, 0}, {410, 200}}
After myFramgeRect: {{1513, 0}, {410, 200}}
Before myFramgeRect: {{1103, 0}, {410, 200}}
After myFramgeRect: {{492, 0}, {410, 200}}
Before myFramgeRect: {{1513, 0}, {410, 200}}
After myFramgeRect: {{902, 0}, {410, 200}}
Before myFramgeRect: {{1923, 0}, {200, 200}}
After myFramgeRect: {{1312, 0},

As you can see, the frame rect's are wildly off!

I've attached my version of the methods from Street Scoller, if anyone can see where I am going wrong or how to correctly change the Frame Rectangles of the Scroll View subviews I'd be eternally grateful!

- (void) recenterContent
{
CGPoint					myCurrentOffset;
CGFloat					myContentWidth;
CGFloat					myCenterOffsetX;
CGFloat					myDistanceFromCenter;
CGPoint					myCenterPosition;
UIView*					myContentView;
CGPoint					myNewContentOffset;
CGRect					myFramgeRect;
CGRect					myBoundingRect;

myCurrentOffset = self.contentOffset;
myContentWidth = self.contentSize.width;

myBoundingRect = self.bounds;
myCenterOffsetX = (myContentWidth - myBoundingRect.size.width) / 2.0;

myDistanceFromCenter = fabs(myCurrentOffset.x - myCenterOffsetX);
if (myDistanceFromCenter > (myContentWidth / 4.0))
	{
	myNewContentOffset = CGPointMake(myCenterOffsetX,myCurrentOffset.y);
//	NSLog(@"myCurrentOffset   : %@",NSStringFromCGPoint(myCurrentOffset));
//	NSLog(@"myNewContentOffset: %@",NSStringFromCGPoint(myNewContentOffset));


	self.contentOffset = myNewContentOffset;

	for (myContentView in self.pContentVisiableArray)
		{
		myFramgeRect = myContentView.frame;
		NSLog(@"Before myFramgeRect: %@",NSStringFromCGRect(myFramgeRect));

		myCenterPosition = [self.pContentContainerView convertPoint:myContentView.center toView:self];
		myCenterPosition.x += (myCenterOffsetX - myCurrentOffset.x);
		myContentView.center = [self convertPoint:myCenterPosition toView:self.pContentContainerView];

		myFramgeRect = myContentView.frame;
		NSLog(@"After myFramgeRect: %@",NSStringFromCGRect(myFramgeRect));
        }
    }
}


- (CGFloat) addViewOnRight:(CGFloat) theRightEdge
{
NSInteger							myContentIndex;
LTWScrollContentInfo*				myContentInfo;
LTWInfinteScrollBaseView*			myBaseView;
CGRect								myFrameRect;
CGFloat								myNewRightEdge;

myContentIndex = [self getContentIndexForXPosition:theRightEdge];
myContentInfo = [self getContentForIndex:myContentIndex];
myBaseView = [self newBaseViewWithContentInfo:myContentInfo];

//*****	NSLog(@"theRightEdge: %f",theRightEdge);
[self dumpContentInfo:myContentInfo withIndex:myContentIndex andMessage:@"addViewOnRight"];

[self.pContentContainerView addSubview:myBaseView];
[self.pContentVisiableArray addObject:myBaseView];

myFrameRect = myBaseView.frame;
myFrameRect.origin.x = theRightEdge;
myFrameRect.origin.y = self.pContentContainerView.bounds.size.height - myFrameRect.size.height;
myBaseView.frame = myFrameRect;
myNewRightEdge = CGRectGetMaxX(myFrameRect);

#if 0
NSLog(@"addViewOnRight");
NSLog(@"theRightEdge:   %f",theRightEdge);
NSLog(@"myNewRightEdge: %f",myNewRightEdge);
NSLog(@"myContentIndex: %d",myContentIndex);
NSLog(@"myFrameRect:    %@",NSStringFromCGRect(myFrameRect));
NSLog(@"--------------------------------------");
NSLog(@"");
#endif

return myNewRightEdge;
}



- (CGFloat) addViewOnLeft:(CGFloat) theLeftEdge
{
NSInteger							myContentIndex;
LTWScrollContentInfo*				myContentInfo;
LTWInfinteScrollBaseView*			myBaseView;
CGRect								myFrameRect;
CGFloat								myLeftEdge;

myContentIndex = [self getContentIndexForXPosition:theLeftEdge];
myContentInfo = [self getContentForIndex:myContentIndex];
myBaseView = [self newBaseViewWithContentInfo:myContentInfo];

//*****	NSLog(@"theLeftEdge: %f",theLeftEdge);
[self dumpContentInfo:myContentInfo withIndex:myContentIndex andMessage:@"addViewOnLeft"];


[self.pContentContainerView addSubview:myBaseView];
//			[self.pContentContainerView insertSubview:myBaseView atIndex:0];
[self.pContentVisiableArray insertObject:myBaseView atIndex:0];

myFrameRect = myBaseView.frame;
myFrameRect.origin.x = theLeftEdge - myFrameRect.size.width;
myFrameRect.origin.y = self.pContentContainerView.bounds.size.height - myFrameRect.size.height;
myBaseView.frame = myFrameRect;
myLeftEdge = CGRectGetMinX(myFrameRect);

return myLeftEdge;
}



- (void) adjustViewsFromMinX:(CGFloat) theMinimumVisibleX toMaxX:(CGFloat) theMaximumVisibleX
{
UIView*								myFirstView;
UIView*								myLastView;
CGFloat								myRightEdgePosition;
CGFloat								myLeftEdgePosition;

if ([self.pContentVisiableArray count] == 0)
    {
	[self addViewOnRight:theMinimumVisibleX];
    }

//**
//**	Add Views that are missing on Right side
//**
myLastView = [self.pContentVisiableArray lastObject];
myRightEdgePosition = CGRectGetMaxX(myLastView.frame);
while (myRightEdgePosition < theMaximumVisibleX)
    {
	myRightEdgePosition = [self addViewOnRight:myRightEdgePosition];
    }

//**
//**	Add Views that are missing on Left side
//**
myFirstView = [self.pContentVisiableArray objectAtIndex:0];
myLeftEdgePosition = CGRectGetMinX(myFirstView.frame);
while (myLeftEdgePosition > theMinimumVisibleX)
    {
	myLeftEdgePosition = [self addViewOnLeft:myLeftEdgePosition];
    }

//**
//**	Remove Views that have Fallen off the Right edge
//**
myLastView = [self.pContentVisiableArray lastObject];
while (myLastView.frame.origin.x > theMaximumVisibleX)
    {
	[myLastView removeFromSuperview];
	[self.pContentVisiableArray removeLastObject];
	myLastView = [self.pContentVisiableArray lastObject];
    }

//**
//**	Remove Views that have Fallen off the Left edge
//**
myFirstView = [self.pContentVisiableArray objectAtIndex:0];
while (CGRectGetMaxX(myFirstView.frame) < theMinimumVisibleX)
    {
	[myFirstView removeFromSuperview];
	[self.pContentVisiableArray removeObjectAtIndex:0];
	myFirstView = [self.pContentVisiableArray objectAtIndex:0];
    }
}




- (void) layoutSubviews
{
CGRect								myVisibleBoundingRect;
CGFloat								myMinimumVisibleX;
CGFloat								myMaximumVisibleX;

[super layoutSubviews];

self.showsHorizontalScrollIndicator = YES;

if (self.pScrollViewInfiniteScrollEnabled == NO)
	return;

if ([self.pContentArray count] == 0)
	return;


[self recenterContent];

myVisibleBoundingRect = [self convertRect:self.bounds toView:self.pContentContainerView];

myMinimumVisibleX = CGRectGetMinX(myVisibleBoundingRect);
myMaximumVisibleX = CGRectGetMaxX(myVisibleBoundingRect);

[self adjustViewsFromMinX:myMinimumVisibleX toMaxX:myMaximumVisibleX];
}

Thanks a lot
Dave






_______________________________________________

Cocoa-dev mailing list (email@hidden)

Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com

Help/Unsubscribe/Update your Subscription:

This email sent to email@hidden


  • Prev by Date: Re: Cocoa class extension best practice
  • Next by Date: Re: Cocoa class extension best practice
  • Previous by thread: Re: Question about matrix of possibilities
  • Next by thread: CFPreferencesAppSynchronize() "looping synchronize attempt"
  • Index(es):
    • Date
    • Thread