Infinite Scroll View Revisited
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