StreetScroller/InfiniteScrollView.m
/* |
File: InfiniteScrollView.m |
Abstract: This view tiles UILabel instances to give the effect of infinite scrolling side to side. |
Version: 1.2 |
Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple |
Inc. ("Apple") in consideration of your agreement to the following |
terms, and your use, installation, modification or redistribution of |
this Apple software constitutes acceptance of these terms. If you do |
not agree with these terms, please do not use, install, modify or |
redistribute this Apple software. |
In consideration of your agreement to abide by the following terms, and |
subject to these terms, Apple grants you a personal, non-exclusive |
license, under Apple's copyrights in this original Apple software (the |
"Apple Software"), to use, reproduce, modify and redistribute the Apple |
Software, with or without modifications, in source and/or binary forms; |
provided that if you redistribute the Apple Software in its entirety and |
without modifications, you must retain this notice and the following |
text and disclaimers in all such redistributions of the Apple Software. |
Neither the name, trademarks, service marks or logos of Apple Inc. may |
be used to endorse or promote products derived from the Apple Software |
without specific prior written permission from Apple. Except as |
expressly stated in this notice, no other rights or licenses, express or |
implied, are granted by Apple herein, including but not limited to any |
patent rights that may be infringed by your derivative works or by other |
works in which the Apple Software may be incorporated. |
The Apple Software is provided by Apple on an "AS IS" basis. APPLE |
MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION |
THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS |
FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND |
OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. |
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL |
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, |
MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED |
AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), |
STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE |
POSSIBILITY OF SUCH DAMAGE. |
Copyright (C) 2013 Apple Inc. All Rights Reserved. |
*/ |
#import "InfiniteScrollView.h" |
@interface InfiniteScrollView () |
@property (nonatomic, strong) NSMutableArray *visibleLabels; |
@property (nonatomic, strong) UIView *labelContainerView; |
@end |
@implementation InfiniteScrollView |
- (id)initWithCoder:(NSCoder *)aDecoder |
{ |
if ((self = [super initWithCoder:aDecoder])) |
{ |
self.contentSize = CGSizeMake(5000, self.frame.size.height); |
_visibleLabels = [[NSMutableArray alloc] init]; |
_labelContainerView = [[UIView alloc] init]; |
self.labelContainerView.frame = CGRectMake(0, 0, self.contentSize.width, self.contentSize.height/2); |
[self addSubview:self.labelContainerView]; |
[self.labelContainerView setUserInteractionEnabled:NO]; |
// hide horizontal scroll indicator so our recentering trick is not revealed |
[self setShowsHorizontalScrollIndicator:NO]; |
} |
return self; |
} |
#pragma mark - Layout |
// recenter content periodically to achieve impression of infinite scrolling |
- (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)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]; |
} |
#pragma mark - Label Tiling |
- (UILabel *)insertLabel |
{ |
UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 500, 80)]; |
[label setNumberOfLines:3]; |
[label setText:@"1024 Block Street\nShaffer, CA\n95014"]; |
[self.labelContainerView addSubview:label]; |
return label; |
} |
- (CGFloat)placeNewLabelOnRight:(CGFloat)rightEdge |
{ |
UILabel *label = [self insertLabel]; |
[self.visibleLabels addObject:label]; // add rightmost label at the end of the array |
CGRect frame = [label frame]; |
frame.origin.x = rightEdge; |
frame.origin.y = [self.labelContainerView bounds].size.height - frame.size.height; |
[label setFrame:frame]; |
return CGRectGetMaxX(frame); |
} |
- (CGFloat)placeNewLabelOnLeft:(CGFloat)leftEdge |
{ |
UILabel *label = [self insertLabel]; |
[self.visibleLabels insertObject:label atIndex:0]; // add leftmost label at the beginning of the array |
CGRect frame = [label frame]; |
frame.origin.x = leftEdge - frame.size.width; |
frame.origin.y = [self.labelContainerView bounds].size.height - frame.size.height; |
[label setFrame:frame]; |
return CGRectGetMinX(frame); |
} |
- (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]; |
} |
} |
@end |
Copyright © 2013 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2013-07-31