I have a UIScrollView with paging in it (so the typical model with a UIPageControl and dragging/flicking left and right between pages), and I've got that working fine. The weird thing is that when I wanted to get rid of bouncing (so that you can't see black behind the UI on the left and right sides), suddenly paging no longer works.
In other words, when:
scrollView.pagingEnabled = YES;
scrollView.bounces = YES;
Everything works fine, except I don't like the bouncing at page(0) and page(length-1). But when I do this:
scrollView.pagingEnabled = YES;
scrollView.bounces = NO;
It stops snapping into place at each page, instead treating all the pages together as one long page. So it seems that for some reason paging is dependent upon bouncing, which is fine as long as I can somehow stop the bouncing. So, is there another way to get rid of it? Or is there something I'm doing wrong?
EDIT:
The solution:
#interface PagingScrollView : UIScrollView
#end
#implementation PagingScrollView
- (id)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame])
{
self.pagingEnabled = YES;
self.bounces = YES;
}
return self;
}
- (void)setContentOffset:(CGPoint)offset
{
CGRect frame = [self frame];
CGSize contentSize = [self contentSize];
CGPoint contentOffset = [self contentOffset];
// Clamp the offset.
if (offset.x <= 0)
offset.x = 0;
else if (offset.x > contentSize.width - frame.size.width)
offset.x = contentSize.width - frame.size.width;
if (offset.y <= 0)
offset.y = 0;
else if (offset.y > contentSize.height - frame.size.height)
offset.y = contentSize.height - frame.size.height;
// Update only if necessary
if (offset.x != contentOffset.x || offset.y != contentOffset.y)
{
[super setContentOffset:offset];
}
}
#end
Your best bet would be to write an UIScrollView subclass and implement the desired behavior manually. You should be able to start with pagingEnabled and bounces both set to YES and then overwrite -setContentOffset: with your own method that clips the edges.
Related
I saw alot of people asking about it, I read everything but still could not figure how to do it. I have a UIScrollView with paging enabled, also I have a NSMutableArray with strings.
what I want to do is that the UIScrollView will show to UITextView with the string and when I change to the next page, it will show the next item on the NSMutableArray.
for some reason I can't get it to work.
- (void)setupPage
{
pagingView.delegate = self;
[self.pagingView setBackgroundColor:[UIColor blackColor]];
[pagingView setCanCancelContentTouches:NO];
pagingView.indicatorStyle = UIScrollViewIndicatorStyleWhite;
pagingView.clipsToBounds = YES;
pagingView.scrollEnabled = YES;
pagingView.pagingEnabled = YES;
pagingView.backgroundColor = [UIColor clearColor];
[pagingView setShowsHorizontalScrollIndicator:NO];
[pagingView setShowsVerticalScrollIndicator:NO];
for (int i=0;i<[arrCat count];i++)
{
UITextView * txtJoke = [[UITextView alloc]initWithFrame:CGRectMake(10, 10, 100, 100)];
[pagingView addSubview:txtJoke];
txtJoke.text = [arrCat objectAtIndex:i];
txtJoke.textAlignment = UITextAlignmentRight;
}
[pagingView setContentSize:CGSizeMake(960, [pagingView bounds].size.height)];
}
- (void)scrollViewDidScroll:(UIScrollView *)_scrollView
{
if (pageControlIsChangingPage) {
return;
}
/*
* We switch page at 50% across
*/
CGFloat pageWidth = _scrollView.frame.size.width;
int page = floor((_scrollView.contentOffset.x - pageWidth / 2) / pageWidth) + 1;
//pageControl.currentPage = page;
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)_scrollView
{
pageControlIsChangingPage = NO;
}
- (IBAction)changePage:(id)sender :(int)current
{
/*
* Change the scroll view
*/
CGRect frame = pagingView.frame;
frame.origin.x = frame.size.width * 1;
frame.origin.y = 0;
[pagingView scrollRectToVisible:frame animated:YES];
/*
* When the animated scrolling finishings, scrollViewDidEndDecelerating will turn this off
*/
pageControlIsChangingPage = YES;
}
This code is a combination of something from another app that i'm trying to fit into this, but without any success.
Please help. Thanks alot.
For one thing, all of your UITextView's have the same frame coordinates, so they will be on top of each other. Assuming you're scrolling horizontally, you need to offset the UITextView's x origin by the page width.
Are you using a UIPageControl? If not, you can just deleted the methods other than setupPage. Those other methods are just used for changing the UIPageControl, and changing the page using the UIPageControl.
Here is a basic scroll view implementation similar to yours:
static NSUInteger kNumberOfPages = 3;
static NSUInteger kPageWidth = 320;
- (void)viewDidLoad
{
[super viewDidLoad];
self.scrollView.pagingEnabled = YES;
self.scrollView.contentSize = CGSizeMake(kPageWidth * kNumberOfPages, self.scrollView.frame.size.height);
self.scrollView.showsHorizontalScrollIndicator = NO;
self.scrollView.showsVerticalScrollIndicator = NO;
self.scrollView.delegate = self;
self.pageOne.frame = CGRectMake(0, 0, kPageWidth, self.pageOne.frame.size.height);
[self.scrollView addSubview:self.pageOne];
self.pageTwo.frame = CGRectMake(kPageWidth, 0, kPageWidth, self.pageTwo.frame.size.height);
[self.scrollView addSubview:self.pageTwo];
self.pageThree.frame = CGRectMake(2*kPageWidth, 0, kPageWidth, self.pageThree.frame.size.height);
[self.scrollView addSubview:self.pageThree];
self.pageControl.numberOfPages = kNumberOfPages;
self.pageControl.currentPage = 0;
}
# pragma mark - Scroll View Related Methods
- (void)scrollViewDidScroll:(UIScrollView *)sender
{
// We don't want a "feedback loop" between the UIPageControl and the scroll delegate in
// which a scroll event generated from the user hitting the page control triggers updates from
// the delegate method. We use a boolean to disable the delegate logic when the page control is used.
if (pageControlUsed_)
{
// do nothing - the scroll was initiated from the page control, not the user dragging
return;
}
// Switch the indicator when more than 50% of the previous/next page is visible
int page = floor((self.scrollView.contentOffset.x - kPageWidth / 2) / kPageWidth) + 1;
self.pageControl.currentPage = page;
}
// At the begin of scroll dragging, reset the boolean used when scrolls originate from the UIPageControl
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
pageControlUsed_ = NO;
}
// At the end of scroll animation, reset the boolean used when scrolls originate from the UIPageControl
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
pageControlUsed_ = NO;
}
- (IBAction)changePage:(id)sender
{
int page = self.pageControl.currentPage;
// update the scroll view to the appropriate page
CGRect frame = self.scrollView.frame;
frame.origin.x = frame.size.width * page;
frame.origin.y = 0;
[self.scrollView scrollRectToVisible:frame animated:YES];
// Set the boolean used when scrolls originate from the UIPageControl. See scrollViewDidScroll: above.
pageControlUsed_ = YES;
}
I have a problem in scaling the uiimageview which is placed inside the uiscrollview. I have googled and checked all the questions related to my problem in StackOverflow as well. I tried all the answers that are posted in the StackOverflow also. Nothing worked for me.
First I am placing the uiimageview inside uiscrollview in nib file and I am taking the image from Camera roll and filling the image view. Then I am using uirotationgesturerecognizer to rotate the image.
Here is the code that I am trying to do.
- (void)viewDidLoad
{
[super viewDidLoad];
NSLog(#"%#",[[UIDevice currentDevice] model]);
// Do any additional setup after loading the view, typically from a nib.
self.imagePicker = [[[UIImagePickerController alloc] init] autorelease];
self.picChosenImageView.layer.shouldRasterize = YES;
self.picChosenImageView.layer.rasterizationScale = [UIScreen mainScreen].scale;
self.picChosenImageView.layer.contents = (id)[UIImage imageNamed:#"test"].CGImage;
self.picChosenImageView.layer.shadowColor = [UIColor blackColor].CGColor;
self.picChosenImageView.layer.shadowOpacity = 0.8f;
self.picChosenImageView.layer.shadowRadius = 8;
self.picChosenImageView.layer.shadowPath = [UIBezierPath bezierPathWithRect:self.picChosenImageView.bounds].CGPath;
UIRotationGestureRecognizer *rotationRecognizer = [[[UIRotationGestureRecognizer alloc]initWithTarget:self
action:#selector(handleRotate:)] autorelease];
rotationRecognizer.delegate = self;
[self.picChosenImageView addGestureRecognizer:rotationRecognizer];
self.containerView.delegate = self;
self.containerView.contentSize = self.picChosenImageView.layer.frame.size;
self.containerView.maximumZoomScale = 4.0f;
self.containerView.minimumZoomScale = 1.0f;
angle = 0.0f;
useRotation = 0.0;
isRotationStarted=FALSE;
isZoomingStarted = FALSE;
}
-(void)lockZoom
{
maximumZoomScale = self.containerView.maximumZoomScale;
minimumZoomScale = self.containerView.minimumZoomScale;
self.containerView.maximumZoomScale = 1.0;
self.containerView.minimumZoomScale = 1.0;
self.containerView.clipsToBounds = false;
self.containerView.scrollEnabled = false;
}
-(void)unlockZoom
{
self.containerView.maximumZoomScale = maximumZoomScale;
self.containerView.minimumZoomScale = minimumZoomScale;
self.containerView.clipsToBounds = true;
self.containerView.scrollEnabled = true;
}
#pragma mark - ScrollView delegate methods
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
{
return self.picChosenImageView;
}
- (void)scrollViewDidZoom:(UIScrollView *)scrollView
{
CGRect frame = self.picChosenImageView.frame;
frame.origin = CGPointZero;
self.picChosenImageView.frame = frame;
//self.picChosenImageView.transform = prevTransform;
}
-(void) scrollViewWillBeginZooming:(UIScrollView *)scrollView withView:(UIView *)view
{
if(!isZoomingStarted)
{
self.picChosenImageView.transform = CGAffineTransformRotate(self.picChosenImageView.transform, angle);
NSLog(#"The zooming started");
isZoomingStarted = TRUE;
CGSize contentSize = self.containerView.bounds.size;
CGRect contentFrame = self.containerView.bounds;
NSLog(#"frame on start: %#", NSStringFromCGRect(contentFrame));
NSLog(#"size on start: %#", NSStringFromCGSize(contentSize));
//prevTransform = self.picChosenImageView.transform;
}
}
-(void) scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale
{
if(isZoomingStarted)
{
self.picChosenImageView.transform = CGAffineTransformRotate(self.picChosenImageView.transform, angle);
isZoomingStarted = FALSE;
CGSize contentSize = self.containerView.contentSize;
CGRect contentFrame = self.containerView.bounds;
NSLog(#"frame on end: %#", NSStringFromCGRect(contentFrame));
NSLog(#"size on end: %#", NSStringFromCGSize(contentSize));
}
}
#pragma mark - GestureRecognizer methods
- (void) handleRotate:(UIRotationGestureRecognizer *)recognizer
{
if(isZoomingStarted == FALSE)
{
if([recognizer state] == UIGestureRecognizerStateBegan)
{
angle = 0.0f;
[self lockZoom];
}
useRotation+= recognizer.rotation;
while( useRotation < -M_PI )
{
useRotation += M_PI*2;
}
while( useRotation > M_PI )
{
useRotation -= M_PI*2;
}
NSLog(#"The rotated value is %f",RADIANS_TO_DEGREES(useRotation));
self.picChosenImageView.transform = CGAffineTransformRotate([self.picChosenImageView transform],
recognizer.rotation);
[recognizer setRotation:0];
if([recognizer state] == UIGestureRecognizerStateEnded)
{
angle = useRotation;
useRotation = 0.0f;
isRotationStarted = FALSE;
self.containerView.hidden = NO;
//prevTransform = self.picChosenImageView.transform;
[self unlockZoom];
}
}
}
My problem is, I am able to successfully do a zoom in and zoom out. I am able to rotate the uiimageview as I wanted to. After rotating the uiimageview to a certain angle, and when I am trying to zoom in, the imageview gets back to the original position (rotate itself back to zero degree) and then the zooming happens. I want to retain the rotation and also zoom. I tried saving the previous transform and assign in back scrollDidzoom and scrollDidBegin delegate methods. None worked. Please help me to spot my mistake which I am overlooking.
try using CGAffineTransformScale instead of just resizing the frame for zooming:
anImage.transform = CGAffineTransformScale(anImage.transform, 2.0, 2.0);
changing the transform for scaling might fix your rotation issue.
hope this helps.
I had the same problem. UIScrollView is taking control over UIImageView and it is using transform without rotation.
So I do not give image reference to scroll and I have added UIPinchGestureRecognizer for scaling.
func viewForZoomingInScrollView(scrollView: UIScrollView) -> UIView? {
return nil
}
Dragging is still working :)
// viewDidLoad
var pinchGestureRecognizer = UIPinchGestureRecognizer(target: self, action: #selector(pinchRecogniezed))
scrollView.addGestureRecognizer(pinchGestureRecognizer)
func pinchRecogniezed(sender: UIPinchGestureRecognizer) {
if sender.state == .Began || sender.state == .Changed {
let scale = sender.scale
imageView.transform = CGAffineTransformScale(imageView.transform, scale, scale)
sender.scale = 1
}
}
I have a UIImageView element as a subview of my main UIScrollView element.
I want the Image to fill out the whole screen at all times, but I can drag the edges a bit too far, so that the yellow "background" becomes visible.
If I lift my finger, the Image "bounces" back and fills out the screen correctly.
I want to prevent this dragging of the image off screen.
I do however want the image to bounce back once I drag it out of the "safety area".
This is my ScrollView initialization:
- (id)initWithFrame:(CGRect)frame{
self = [super initWithFrame:CGRectMake(0, 0, frame.size.height, frame.size.width)];
if (self) {
[self initImageValues];
self.showsVerticalScrollIndicator = NO;
self.showsHorizontalScrollIndicator = NO;
self.bouncesZoom = YES;
self.decelerationRate = UIScrollViewDecelerationRateFast;
self.delegate = self;
self.backgroundColor = [UIColor yellowColor];
self.minimumZoomScale = 1.0;
self.maximumZoomScale = 2.0;
[self setCanCancelContentTouches:YES];
self.clipsToBounds = YES;
// Load Image
imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:imageName]];
[self addSubview:imageView];
[self setContentSize: CGSizeMake(imageView.frame.size.width, imageView.frame.size.height)];
// Set bigger "bounce" zone (safety area)
self.contentInset=UIEdgeInsetsMake(-SAFETY_ZONE,-SAFETY_ZONE,-SAFETY_ZONE,-SAFETY_ZONE);
self.scrollEnabled = YES;
}
return self;}
Use these delegate methods:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
Then read the offset and return if it's out of the "safety area".
Could be that you need another delegate method from the UIScrollView though, but a workaround like this should fix your problem :)
try setting bounces on the scrollview:
self.bounces = NO;
TRY this:
**
self.scrollView.maximumZoomScale = 1.0;
self.scrollView.minimumZoomScale = 1.0;
**
Try setting its bounces property to NO.
So I want to make a sliding image view gallery with preview of next and previous images. I want it to show partly the next image (if available) and the previous image (if there was one). What do I mean exactly? Assuming there are 3 images at all.
the very 1st image. So I want the 1st image to display at center (horizontally) and a part of next image as well, so user could see there is at least 1 image remaining.
Schematically: [&&&] [
the 2nd image. I want to show a part of 1st image (at left side) and a 2nd image (at center) and a part of 3rd image (at right). So any user could easily determine there is at least 1 image behind and at least 1 image remains.
Schematically: ] [###] [
the 3rd and last image. There are a part of 2nd image (at left) and 3rd image at center. So there is no doubt that there is no image remains and at least 1 image behind.
Schematically: ] [###].
So how to implement this idea in the right way?
I did it so:
// ItemGalleryVC.h
#import <UIKit/UIKit.h>
#interface ItemGalleryVC : UIViewController <UIScrollViewDelegate>{
IBOutlet UIScrollView *scrollView;
IBOutlet UIPageControl *pageControl;
BOOL pageControlIsChangingPage;
}
#property (nonatomic, retain) IBOutlet UIScrollView *scrollView;
#property (nonatomic, retain) IBOutlet UIPageControl *pageControl;
- (IBAction)changePage:(id)sender;
- (void)setupPage;
#end
and
// ItemGalleryVC.m
#import "ItemGalleryVC.h"
#implementation ItemGalleryVC
#synthesize scrollView;
#synthesize pageControl;
- (void)viewDidLoad
{
[self setupPage];
[super viewDidLoad];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
}
- (void)viewDidUnload
{
[scrollView release];
[pageControl release];
}
- (void)dealloc
{
[super dealloc];
}
- (void)setupPage
{
NSLog(#"setupPage");
scrollView.delegate = self;
[scrollView setCanCancelContentTouches:NO];
scrollView.indicatorStyle = UIScrollViewIndicatorStyleWhite;
scrollView.clipsToBounds = YES;
scrollView.scrollEnabled = YES;
scrollView.pagingEnabled = YES;
NSUInteger nimages = 0;
CGFloat cx = 0; //the total scroll width
CGRect rect = scrollView.frame; //image frame
rect.size.height = scrollView.frame.size.height;
rect.size.width = scrollView.frame.size.width - 100; //50px at left and right sides
rect.origin.y = scrollView.frame.origin.y+5; //down by 5 px
for (; ; nimages++) {
NSString *imageName = [NSString stringWithFormat:#"item_image0%d.jpg", (nimages + 1)];
UIImage *image = [UIImage imageNamed:imageName];
if (image == nil) {
NSLog(#"no more imagez");
break;
}
NSLog(#"setting up img: %#",imageName);
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
rect.origin.x = cx+50; //move right by 50px
imageView.frame = rect;
imageView.contentMode = UIViewContentModeScaleAspectFit;
imageView.backgroundColor = [UIColor blackColor];
[scrollView addSubview:imageView];
[imageView release];
cx += rect.size.width+30;
}
self.pageControl.numberOfPages = nimages;
// for the last image to be centered frame need to be wider by 100px
[scrollView setContentSize:CGSizeMake(cx+100, [scrollView bounds].size.height)];
}
- (void)scrollViewDidScroll:(UIScrollView *)_scrollView
{
if (pageControlIsChangingPage) {
return;
}
// calc page number according to its frame size
CGFloat pageWidth = _scrollView.frame.size.width;
int page = floor((_scrollView.contentOffset.x - pageWidth / 2) / pageWidth) + 1;
pageControl.currentPage = page;
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)_scrollView
{
pageControlIsChangingPage = NO;
// this code added to correct image position (frame width) when using touch sliding
CGRect frame = scrollView.frame;
frame.origin.x = (frame.size.width-70) * pageControl.currentPage;
frame.origin.y = 0;
[scrollView scrollRectToVisible:frame animated:YES];
}
- (IBAction)changePage:(id)sender
{
/*
* Change the scroll view
*/
CGRect frame = scrollView.frame;
frame.origin.x = (frame.size.width-70) * pageControl.currentPage;
frame.origin.y = 0;
[scrollView scrollRectToVisible:frame animated:YES];
pageControlIsChangingPage = NO;
}
#end
The problem is the frame size (it's width). Its ScrollView.frame size but i need it smaller (less wide).
With this code If I use page control only to switch images everything works fine and animation looks right. But if I use sliding its totally bad.
The code above contains a fix (in scrollViewDidEndDecelerating) but it still works not native while sliding and it works good for 3 images. It slides to incorrect position and than it move a bit back. If there are more than 3 images slider wont slide to the last image.
I'm curious if there is some other and proper way to do it.
Actually I found another soultion - http://www.cocoacontrols.com/platforms/ios/controls/hgpagescrollview
this control is what I want.
Try, and the scrollView will draw outside its bounds.
[scrollView setClipsToBounds:NO];
I've implemented an infinite UIScrollView that contains UIViews. When scrolling in iOS5 (simulator and iphone) it works beautifully. But in iOS 4.3 (sim and phone) it kinda goes a bit crazy and scrolls passed more views than it should (about 10-15x more views than in iOS5). And to make it more strange it works as expected if I drag slowly so it doesn't continue scroll after I release.
The setup is kinda simple, I extend the UIScrollView class. And add UIView with a width of about 1500pt, in this I add my UIViews I want to scroll over. When scrolling the views are added and removed if they are off screen.
I think my problem lies with in this code:
- (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);
UIView *image;
if (distanceFromCenter > (contentWidth / 4.0)) {
// Recenter the ScrollView so we never reach the edge
self.contentOffset = CGPointMake(centerOffsetX, currentOffset.y);
// Move visiblePhotos to new position to adjust to new contentOffset
for (image in visiblePhotos) {
CGPoint center = image.center;
center.x += (centerOffsetX - currentOffset.x);
image.center = center;
}
}
}
recenterIfNecessary is invoked here:
- (void)layoutSubviews {
[super layoutSubviews];
[self recenterIfNecessary];
[self tileImagesFromMinX:minVisibleX toMaxX:maxVisibleX];
//....
}
I've tried logging positions and offsets to try find a pattern with the difference between iOS4 & 5 with luck.
I got the concept and some code from http://developer.apple.com/videos/wwdc/2011/#advanced-scrollview-techniques (they use iOS5).
EDIT
Photos (that are put in the visiblePhotos array) are created with this methods:
- (CGFloat)placeNewImageOnRight:(CGFloat)rightEdge {
imageIndex = imageIndex < 0 ? ([reportItems count] - 1) : (imageIndex >= [reportItems count] ? 0 : imageIndex);
UIView *image = [self createItemContainer:[reportItems objectAtIndex:imageIndex]];
[visiblePhotos addObject:image];
CGRect frame = [image frame];
frame.origin.x = rightEdge;
frame.origin.y = [imageContainerView bounds].size.height - frame.size.height;
[image setFrame:frame];
imageIndex += 1;
return CGRectGetMaxX(frame);
}
- (CGFloat)placeNewImageOnLeft:(CGFloat)leftEdge {
imageIndex = imageIndex < 0 ? ([reportItems count] - 1) : (imageIndex >= [reportItems count] ? 0 : imageIndex);
UIView *image = [self createItemContainer:[reportItems objectAtIndex:imageIndex]];
[visiblePhotos insertObject:image atIndex:0];
CGRect frame = [image frame];
frame.origin.x = leftEdge - frame.size.width;
frame.origin.y = [imageContainerView bounds].size.height - frame.size.height;
[image setFrame:frame];
imageIndex -= 1;
return CGRectGetMinX(frame);
}
[self createItemContainer:[reportItems objectAtIndex:imageIndex]] creates a UIVeiw that contains a photo`.
The left and right methods are basically the same and are invoked by the method below. And tileImagesFromMinX is invoked on every frame by - (void)layoutSubviews.
- (void)tileImagesFromMinX:(CGFloat)minVisibleX toMaxX:(CGFloat)maxVisibleX {
if ([visiblePhotos count] == 0) {
[self placeNewImageOnRight:minVisibleX];
}
UIView *lastImage = [visiblePhotos lastObject];
CGFloat rightEdge = CGRectGetMaxX([lastImage frame]);
while (rightEdge < maxVisibleX) {
rightEdge = [self placeNewImageOnRight:rightEdge];
}
UIView *firstImage = [visiblePhotos objectAtIndex:0];
CGFloat leftEdge = CGRectGetMinX([firstImage frame]);
while (leftEdge > minVisibleX) {
leftEdge = [self placeNewImageOnLeft:leftEdge];
}
lastImage = [visiblePhotos lastObject];
while ([lastImage frame].origin.x > maxVisibleX) {
[lastImage removeFromSuperview];
[visiblePhotos removeLastObject];
lastImage = [visiblePhotos lastObject];
}
firstImage = [visiblePhotos objectAtIndex:0];
while (CGRectGetMaxX([firstImage frame]) < minVisibleX) {
[firstImage removeFromSuperview];
[visiblePhotos removeObjectAtIndex:0];
firstImage = [visiblePhotos objectAtIndex:0];
}
}
I've done a bunch of investigating and determined that you can't just set self.contentOffset = ...; and expect the scroll view to scroll/decelerate properly in iOS 4.3. So, even without the image tiling (and in fact you will notice this more clearly if you remove your images and just put a background on your image container view), your implementation of recenterIfNecessary will not work under 4.3 (this also affects the sample code on the developer site!).
Unfortunately, it seems the only way to work around this is to override -setContentOffset: and modify the offset there before calling super. Have a look at this question for some guidance on how to get that working correctly. I hope for your sake that this method will also work in iOS 5...