Infinite UIScrollView gets strange behavior in iOS4.3 not iOS5 - iphone

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...

Related

Scaling UIImageView inside UIScrollView with maintaining the rotation

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
}
}

Need to create photo slider using photo library photos in iphone

I want photo slider like whts app has. It should access all the pics or some of the pics from in build photo library and it should be scrollable using scroll view.
I have tried with different photo viewer but everything has memory issue. So not sure if anyone has it's working solution. Really appreciate your help.
Thank you,
Ankita
This is working almost fine, but one small problem with backbord direction scrolling. Just try this out may be you can find some direction to go and may be find better solution using it.
(Have used ELCImagePicker to show first time all images and then on selecting one present the scrollview to show large images at a time 10 images are shown also have tweak the delegate method of ELCImagePickerController to get the index of selected image)
At first load ie when select image from ELCImagePickerController
- (void)elcImagePickerController:(ELCImagePickerController *)picker didFinishPickingMediaWithInfo:(NSArray *)info selectedIndex:(NSInteger)_index {
[self dismissModalViewControllerAnimated:NO];
for (UIView *v in [scrollview subviews]) {
[v removeFromSuperview];
}
infoItems = [[NSArray alloc]initWithArray:info];
CGRect workingFrame = scrollview.frame;
workingFrame.origin.x = 0;
int indexFrame = picker.selectedIndex;
newIndex = indexFrame;
for(int i = 0; i < [info count]; i++)
{
NSDictionary *dict = [info objectAtIndex:i];
UIImageView *imageview = [[UIImageView alloc] initWithImage:[dict objectForKey:UIImagePickerControllerOriginalImage]];
[imageview setContentMode:UIViewContentModeScaleAspectFit];
imageview.frame = workingFrame;
imageview.tag = i;
if(i >= indexFrame && i <= indexFrame + 9)
{
[scrollview addSubview:imageview];
workingFrame.origin.x = workingFrame.origin.x + workingFrame.size.width;
}
}
[scrollview setPagingEnabled:YES];
[scrollview setContentSize:CGSizeMake(workingFrame.origin.x, workingFrame.size.height)];
[scrollview scrollRectToVisible:CGRectMake(0, 0, workingFrame.size.width, workingFrame.size.height) animated:NO];
self.scrollview.hidden = NO;
self.pageControll.hidden = NO;
self.pageControll.numberOfPages = [[scrollview subviews]count];
index = 0;
lastContentOffset = scrollview.contentOffset.x;
NSLog(#"lastContentOffset %.2f",lastContentOffset);
}
After that on scrollViews delegate method
-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
NSLog(#"ScrollViewDidEndDecelerating is called with subview(s) %d",[[scrollview subviews]count]);
if(lastContentOffset < scrollView.contentOffset.x)
{
index += (int)(scrollview.contentOffset.x - lastContentOffset)/scrollview.frame.size.width;
newIndex += (int)(scrollview.contentOffset.x - lastContentOffset)/scrollview.frame.size.width;
self.pageControll.currentPage = index;
NSLog(#"Index incremented %d \n newIndex %d",index,newIndex);
if(index == [[scrollView subviews]count]-1)
{
if(newIndex < [infoItems count]-1)
{
[self performSelector:#selector(reloadScrollViewWithNewImages)];
}
}
[Appirater userDidSignificantEvent:YES];
}
else if(lastContentOffset > scrollView.contentOffset.x)
{
index -= (int)(lastContentOffset - scrollview.contentOffset.x)/scrollview.frame.size.width;
newIndex -= (int)(int)(lastContentOffset - scrollview.contentOffset.x)/scrollview.frame.size.width;
self.pageControll.currentPage = index;
NSLog(#"Index decremented %d \n newIndex %d",index,newIndex);
if(index == 0)
{
if(newIndex > 0)
{
newIndex -= 9;
if(newIndex < 0)
newIndex = 0;
[self performSelector:#selector(reloadScrollViewWithNewImages)];
}
}
[Appirater userDidSignificantEvent:YES];
}
lastContentOffset = scrollView.contentOffset.x;
NSLog(#"New lastContentOffset %.2f",lastContentOffset);
}
and the method used for reload the scrollView is as follow
-(void)reloadScrollViewWithNewImages
{
for (UIView *v in [scrollview subviews]) {
[v removeFromSuperview];
}
CGRect workingFrame = scrollview.frame;
workingFrame.origin.x = 0;
NSLog(#"reloadScrollView newIndex %d",newIndex);
int indexFrame = newIndex;
for(int i = 0; i < [infoItems count]; i++)
{
NSDictionary *dict = [infoItems objectAtIndex:i];
UIImageView *imageview = [[UIImageView alloc] initWithImage:[dict objectForKey:UIImagePickerControllerOriginalImage]];
[imageview setContentMode:UIViewContentModeScaleAspectFit];
imageview.frame = workingFrame;
imageview.tag = i;
if(i >= indexFrame && i <= indexFrame + 9)
{
[scrollview addSubview:imageview];
workingFrame.origin.x = workingFrame.origin.x + workingFrame.size.width;
}
}
[scrollview setPagingEnabled:YES];
[scrollview setContentSize:CGSizeMake(workingFrame.origin.x, workingFrame.size.height)];
[scrollview scrollRectToVisible:CGRectMake(0, 0, workingFrame.size.width, workingFrame.size.height) animated:NO];
self.scrollview.hidden = NO;
self.pageControll.hidden = NO;
index = 0;
self.pageControll.numberOfPages = [[scrollview subviews]count];
NSLog(#"number %d",[[scrollview subviews]count]);
lastContentOffset = scrollview.contentOffset.x;
NSLog(#"reloadScrollView's lastContentOffset %.2f",lastContentOffset);
}
and all used properties and private ivars declared in .h file's interface part required in this code are
Private ivars
NSInteger index;
float lastContentOffset;
Properties
#property (nonatomic,strong) IBOutlet UIScrollView *scrollview;
#property (strong, nonatomic) NSArray *infoItems;
#property (assign, atomic) int newIndex;
Happy Coding :)
You can use CATiledLayer to draw image. CATiledLayer can be used to increase the performance of paging, panning, and zooming with high-resolution images or large sets of photos.
As i commented above you can go by reusable view approach means at a time only three imagview or view will be in memory. Let say you are showing photo number:2 so, you should have to keep photo:1,2 and3 in memory(i.e. to avoid flicker) no need to load all photos at a time. Suppose you are scrolling to photo number:3 then your logic should discard photo number:1 and should load photo number:2,3,4. If you are still not getting or don't know how to do this programmatically then don't worry. Apple's good sample code is available that is doing all you want.
Sample code
In above sample code they have displayed two way of displaying image.
One is directly showing/setting the image into image view.
Second is with drawing image with TileView using this method: - (void)displayTiledImageNamed:(NSString *)imageName size:(CGSize)imageSize
If you are thinking lot of about memory then definitely you have to use second approach.

How to customize paging in UIScrollView?

I have a question in scroll view.
Right now I wrote a sample about image gallery with scroll view. I have plenty of images added into a scroll view. Each time, it display 3 images, the question is how can measure the scrolling properly. For example: the minimum each scroll is moving 1 image. Right now, I think each time I scroll, the minimum images moving are 3. That make me can't stop at the right image I want to see.
Below is the code.
- (void)layoutScrollImages
{
UIImageView *view = nil;
NSArray *subviews = [scroll subviews];
// reposition all image subviews in a horizontal serial fashion
CGFloat curXLoc = 0;
for (view in subviews)
{
if ([view isKindOfClass:[UIImageView class]] && view.tag > 0)
{
CGRect frame = view.frame;
frame.origin = CGPointMake(curXLoc, 0);
view.frame = frame;
curXLoc += (kScrollObjWidth);
}
}
// set the content size so it can be scrollable
[scroll setContentSize:CGSizeMake((kNumImages * kScrollObjWidth), [scroll bounds].size.height)];
}
#pragma mark - View lifecycle
- (void)viewDidLoad
{
[super viewDidLoad];
// 1. setup the scrollview for multiple images and add it to the view controller
//
// note: the following can be done in Interface Builder, but we show this in code for clarity
[scroll setBackgroundColor:[UIColor blackColor]];
[scroll setCanCancelContentTouches:NO];
scroll.indicatorStyle = UIScrollViewIndicatorStyleWhite;
scroll.clipsToBounds = YES; // default is NO, we want to restrict drawing within our scrollview
scroll.scrollEnabled = YES;
// pagingEnabled property default is NO, if set the scroller will stop or snap at each photo
// if you want free-flowing scroll, don't set this property.
scroll.pagingEnabled = YES;
// load all the images from our bundle and add them to the scroll view
NSUInteger i;
for (i = 1; i <= kNumImages; i++)
{
NSString *imageName = [NSString stringWithFormat:#"image%d.jpg", i];
UIImage *image = [UIImage imageNamed:imageName];
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
// setup each frame to a default height and width, it will be properly placed when we call "updateScrollList"
CGRect rect = imageView.frame;
rect.size.height = kScrollObjHeight;
rect.size.width = kScrollObjWidth;
imageView.frame = rect;
imageView.tag = i; // tag our images for later use when we place them in serial fashion
[scroll addSubview:imageView];
[imageView release];
}
[self layoutScrollImages];
}
Use this code......
-(void)scrollViewDidScroll:(UIScrollView *)scrollView
{
if(scroll.contentOffset.y> 320)
{
int y = scrollView.contentOffset.y;
y = y/3;
[scrollView setContentOffset:CGPointMake(0, y)];
}
}
Hope, this will help you...Chill
Subclass the content view and overwrite this function:
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
if ([self pointInside:point withEvent:event]) {
if ([[self subviews] count] > 0) {
//force return of first child, if exists
return [[self subviews] objectAtIndex:0];
} else {
return self;
}
}
return nil; }
See detail at https://github.com/taufikobet/ScrollViewCustomPaging.

Fitting an Image to Screen on Rotation iPhone / iPad?

I have been playing around with one of the iPhone examples from Apple' web site (ScrollViewSuite) . I am trying to tweak it a bit so that when I rotate the the iPad the image will fit into the screen in landscape mode vertical. I have been successful in getting the image to rotate, but the image is larger than the height of the landscape screen, so the bottom is below the screen. I would like to image to scale to the height of the landscape screen.
I have been playing around with various autoSizingMask attributes without success.
The imageView is called "zoomView" this is the actual image which loads into a scrollView called imageScrollView.
I am trying to achieve the screen to rotate and look like this.... sorry only 1 link allowed new user :(
olsonvox.com/photos/correct.png
However, this is what My screen is looking like.
http://www.olsonvox.com/photos/incorrect.png
I would really appreciate some advice or guidance. Below is the RootViewController.m for the project.
Blade
#
import "RootViewController.h"
#define ZOOM_VIEW_TAG 100
#define ZOOM_STEP 1.5
#define THUMB_HEIGHT 150
#define THUMB_V_PADDING 25
#define THUMB_H_PADDING 25
#define CREDIT_LABEL_HEIGHT 25
#define AUTOSCROLL_THRESHOLD 30
#interface RootViewController (ViewHandlingMethods)
- (void)toggleThumbView;
- (void)pickImageNamed:(NSString *)name;
- (NSArray *)imageNames;
- (void)createThumbScrollViewIfNecessary;
- (void)createSlideUpViewIfNecessary;
#end
#interface RootViewController (AutoscrollingMethods)
- (void)maybeAutoscrollForThumb:(ThumbImageView *)thumb;
- (void)autoscrollTimerFired:(NSTimer *)timer;
- (void)legalizeAutoscrollDistance;
- (float)autoscrollDistanceForProximityToEdge:(float)proximity;
#end
#interface RootViewController (UtilityMethods)
- (CGRect)zoomRectForScale:(float)scale withCenter:(CGPoint)center;
#end
#implementation RootViewController
- (void)loadView {
[super loadView];
imageScrollView = [[UIScrollView alloc] initWithFrame:[[self view]bounds]];
// this code makes the image resize to the width and height properly.
imageScrollView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin| UIViewAutoresizingFlexibleBottomMargin| UIViewAutoresizingFlexibleBottomMargin;
// TRY SETTNG CENTER HERE SOMEHOW>....
[imageScrollView setBackgroundColor:[UIColor blackColor]];
[imageScrollView setDelegate:self];
[imageScrollView setBouncesZoom:YES];
[[self view] addSubview:imageScrollView];
[self toggleThumbView];
// intitializes with the first image.
[self pickImageNamed:#"lookbook1"];
}
- (void)dealloc {
[imageScrollView release];
[slideUpView release];
[thumbScrollView release];
[super dealloc];
}
#pragma mark UIScrollViewDelegate methods
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
UIView *view = nil;
if (scrollView == imageScrollView) {
view = [imageScrollView viewWithTag:ZOOM_VIEW_TAG];
}
return view;
}
/************************************** NOTE **************************************/
/* The following delegate method works around a known bug in zoomToRect:animated: */
/* In the next release after 3.0 this workaround will no longer be necessary */
/**********************************************************************************/
- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale {
[scrollView setZoomScale:scale+0.01 animated:NO];
[scrollView setZoomScale:scale animated:NO];
}
#pragma mark TapDetectingImageViewDelegate methods
- (void)tapDetectingImageView:(TapDetectingImageView *)view gotSingleTapAtPoint:(CGPoint)tapPoint {
// Single tap shows or hides drawer of thumbnails.
[self toggleThumbView];
}
- (void)tapDetectingImageView:(TapDetectingImageView *)view gotDoubleTapAtPoint:(CGPoint)tapPoint {
// double tap zooms in
float newScale = [imageScrollView zoomScale] * ZOOM_STEP;
CGRect zoomRect = [self zoomRectForScale:newScale withCenter:tapPoint];
[imageScrollView zoomToRect:zoomRect animated:YES];
}
- (void)tapDetectingImageView:(TapDetectingImageView *)view gotTwoFingerTapAtPoint:(CGPoint)tapPoint {
// two-finger tap zooms out
float newScale = [imageScrollView zoomScale] / ZOOM_STEP;
CGRect zoomRect = [self zoomRectForScale:newScale withCenter:tapPoint];
[imageScrollView zoomToRect:zoomRect animated:YES];
}
#pragma mark ThumbImageViewDelegate methods
- (void)thumbImageViewWasTapped:(ThumbImageView *)tiv {
[self pickImageNamed:[tiv imageName]];
[self toggleThumbView];
}
- (void)thumbImageViewStartedTracking:(ThumbImageView *)tiv {
[thumbScrollView bringSubviewToFront:tiv];
}
// CONTROLS DRAGGING AND DROPPING THUMBNAILS...
- (void)thumbImageViewMoved:(ThumbImageView *)draggingThumb {
// check if we've moved close enough to an edge to autoscroll, or far enough away to stop autoscrolling
[self maybeAutoscrollForThumb:draggingThumb];
/* The rest of this method handles the reordering of thumbnails in the thumbScrollView. See */
/* ThumbImageView.h and ThumbImageView.m for more information about how this works. */
// we'll reorder only if the thumb is overlapping the scroll view
if (CGRectIntersectsRect([draggingThumb frame], [thumbScrollView bounds])) {
BOOL draggingRight = [draggingThumb frame].origin.x > [draggingThumb home].origin.x ? YES : NO;
/* we're going to shift over all the thumbs who live between the home of the moving thumb */
/* and the current touch location. A thumb counts as living in this area if the midpoint */
/* of its home is contained in the area. */
NSMutableArray *thumbsToShift = [[NSMutableArray alloc] init];
// get the touch location in the coordinate system of the scroll view
CGPoint touchLocation = [draggingThumb convertPoint:[draggingThumb touchLocation] toView:thumbScrollView];
// calculate minimum and maximum boundaries of the affected area
float minX = draggingRight ? CGRectGetMaxX([draggingThumb home]) : touchLocation.x;
float maxX = draggingRight ? touchLocation.x : CGRectGetMinX([draggingThumb home]);
// iterate through thumbnails and see which ones need to move over
for (ThumbImageView *thumb in [thumbScrollView subviews]) {
// skip the thumb being dragged
if (thumb == draggingThumb) continue;
// skip non-thumb subviews of the scroll view (such as the scroll indicators)
if (! [thumb isMemberOfClass:[ThumbImageView class]]) continue;
float thumbMidpoint = CGRectGetMidX([thumb home]);
if (thumbMidpoint >= minX && thumbMidpoint <= maxX) {
[thumbsToShift addObject:thumb];
}
}
// shift over the other thumbs to make room for the dragging thumb. (if we're dragging right, they shift to the left)
float otherThumbShift = ([draggingThumb home].size.width + THUMB_H_PADDING) * (draggingRight ? -1 : 1);
// as we shift over the other thumbs, we'll calculate how much the dragging thumb's home is going to move
float draggingThumbShift = 0.0;
// send each of the shifting thumbs to its new home
for (ThumbImageView *otherThumb in thumbsToShift) {
CGRect home = [otherThumb home];
home.origin.x += otherThumbShift;
[otherThumb setHome:home];
[otherThumb goHome];
draggingThumbShift += ([otherThumb frame].size.width + THUMB_H_PADDING) * (draggingRight ? 1 : -1);
}
// change the home of the dragging thumb, but don't send it there because it's still being dragged
CGRect home = [draggingThumb home];
home.origin.x += draggingThumbShift;
[draggingThumb setHome:home];
}
}
- (void)thumbImageViewStoppedTracking:(ThumbImageView *)tiv {
// if the user lets go of the thumb image view, stop autoscrolling
[autoscrollTimer invalidate];
autoscrollTimer = nil;
}
#pragma mark Autoscrolling methods
- (void)maybeAutoscrollForThumb:(ThumbImageView *)thumb {
autoscrollDistance = 0;
// only autoscroll if the thumb is overlapping the thumbScrollView
if (CGRectIntersectsRect([thumb frame], [thumbScrollView bounds])) {
CGPoint touchLocation = [thumb convertPoint:[thumb touchLocation] toView:thumbScrollView];
float distanceFromLeftEdge = touchLocation.x - CGRectGetMinX([thumbScrollView bounds]);
float distanceFromRightEdge = CGRectGetMaxX([thumbScrollView bounds]) - touchLocation.x;
if (distanceFromLeftEdge < AUTOSCROLL_THRESHOLD) {
autoscrollDistance = [self autoscrollDistanceForProximityToEdge:distanceFromLeftEdge] * -1; // if scrolling left, distance is negative
} else if (distanceFromRightEdge < AUTOSCROLL_THRESHOLD) {
autoscrollDistance = [self autoscrollDistanceForProximityToEdge:distanceFromRightEdge];
}
}
// if no autoscrolling, stop and clear timer
if (autoscrollDistance == 0) {
[autoscrollTimer invalidate];
autoscrollTimer = nil;
}
// otherwise create and start timer (if we don't already have a timer going)
else if (autoscrollTimer == nil) {
autoscrollTimer = [NSTimer scheduledTimerWithTimeInterval:(1.0 / 60.0)
target:self
selector:#selector(autoscrollTimerFired:)
userInfo:thumb
repeats:YES];
}
}
- (float)autoscrollDistanceForProximityToEdge:(float)proximity {
// the scroll distance grows as the proximity to the edge decreases, so that moving the thumb
// further over results in faster scrolling.
return ceilf((AUTOSCROLL_THRESHOLD - proximity) / 5.0);
}
- (void)legalizeAutoscrollDistance {
// makes sure the autoscroll distance won't result in scrolling past the content of the scroll view
float minimumLegalDistance = [thumbScrollView contentOffset].x * -1;
float maximumLegalDistance = [thumbScrollView contentSize].width - ([thumbScrollView frame].size.width + [thumbScrollView contentOffset].x);
autoscrollDistance = MAX(autoscrollDistance, minimumLegalDistance);
autoscrollDistance = MIN(autoscrollDistance, maximumLegalDistance);
}
- (void)autoscrollTimerFired:(NSTimer*)timer {
[self legalizeAutoscrollDistance];
// autoscroll by changing content offset
CGPoint contentOffset = [thumbScrollView contentOffset];
contentOffset.x += autoscrollDistance;
[thumbScrollView setContentOffset:contentOffset];
// adjust thumb position so it appears to stay still
ThumbImageView *thumb = (ThumbImageView *)[timer userInfo];
[thumb moveByOffset:CGPointMake(autoscrollDistance, 0)];
}
#pragma mark View handling methods
- (void)toggleThumbView {
[self createSlideUpViewIfNecessary]; // no-op if slideUpView has already been created
CGRect frame = [slideUpView frame];
if (thumbViewShowing) {
frame.origin.y = 0;
} else {
frame.origin.y = -225;
}
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:0.3];
[slideUpView setFrame:frame];
[UIView commitAnimations];
thumbViewShowing = !thumbViewShowing;
}
- (void)pickImageNamed:(NSString *)name {
// first remove previous image view, if any
[[imageScrollView viewWithTag:ZOOM_VIEW_TAG] removeFromSuperview];
UIImage *image = [UIImage imageNamed:[NSString stringWithFormat:#"%#.jpg", name]];
TapDetectingImageView *zoomView = [[TapDetectingImageView alloc] initWithImage:image];
zoomView.autoresizingMask = UIViewAutoresizingFlexibleWidth ;
[zoomView setDelegate:self];
[zoomView setTag:ZOOM_VIEW_TAG];
[imageScrollView addSubview:zoomView];
[imageScrollView setContentSize:[zoomView frame].size];
[zoomView release];
// choose minimum scale so image width fits screen
float minScale = [imageScrollView frame].size.width / [zoomView frame].size.width;
[imageScrollView setMinimumZoomScale:minScale];
[imageScrollView setZoomScale:minScale];
[imageScrollView setContentOffset:CGPointZero];
}
- (NSArray *)imageNames {
// the filenames are stored in a plist in the app bundle, so create array by reading this plist
NSString *path = [[NSBundle mainBundle] pathForResource:#"Images" ofType:#"plist"];
NSData *plistData = [NSData dataWithContentsOfFile:path];
NSString *error; NSPropertyListFormat format;
NSArray *imageNames = [NSPropertyListSerialization propertyListFromData:plistData
mutabilityOption:NSPropertyListImmutable
format:&format
errorDescription:&error];
if (!imageNames) {
NSLog(#"Failed to read image names. Error: %#", error);
[error release];
}
return imageNames;
}
- (void)createSlideUpViewIfNecessary {
if (!slideUpView) {
[self createThumbScrollViewIfNecessary];
CGRect bounds = [[self view] bounds];
float thumbHeight = [thumbScrollView frame].size.height;
float labelHeight = CREDIT_LABEL_HEIGHT;
// create label giving credit for images
UILabel *creditLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, thumbHeight, bounds.size.width, labelHeight)];
[creditLabel setBackgroundColor:[UIColor clearColor]];
[creditLabel setTextColor:[UIColor whiteColor]];
// [creditLabel setFont:[UIFont fontWithName:#"Helvetica" size:16]];
// [creditLabel setText:#"SAMPLE TEXT"];
[creditLabel setTextAlignment:UITextAlignmentCenter];
// create container view that will hold scroll view and label
CGRect frame = CGRectMake(0.0, -225.00, bounds.size.width+256, thumbHeight + labelHeight);
slideUpView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin;
slideUpView = [[UIView alloc] initWithFrame:frame];
[slideUpView setBackgroundColor:[UIColor blackColor]];
[slideUpView setOpaque:NO];
[slideUpView setAlpha:.75];
[[self view] addSubview:slideUpView];
// add subviews to container view
[slideUpView addSubview:thumbScrollView];
[slideUpView addSubview:creditLabel];
[creditLabel release];
}
}
- (void)createThumbScrollViewIfNecessary {
if (!thumbScrollView) {
float scrollViewHeight = THUMB_HEIGHT + THUMB_V_PADDING;
float scrollViewWidth = [[self view] bounds].size.width;
thumbScrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, scrollViewWidth, scrollViewHeight)];
[thumbScrollView setCanCancelContentTouches:NO];
[thumbScrollView setClipsToBounds:NO];
// now place all the thumb views as subviews of the scroll view
// and in the course of doing so calculate the content width
float xPosition = THUMB_H_PADDING;
for (NSString *name in [self imageNames]) {
UIImage *thumbImage = [UIImage imageNamed:[NSString stringWithFormat:#"%#_thumb.jpg", name]];
if (thumbImage) {
ThumbImageView *thumbView = [[ThumbImageView alloc] initWithImage:thumbImage];
[thumbView setDelegate:self];
[thumbView setImageName:name];
CGRect frame = [thumbView frame];
frame.origin.y = THUMB_V_PADDING;
frame.origin.x = xPosition;
[thumbView setFrame:frame];
[thumbView setHome:frame];
[thumbScrollView addSubview:thumbView];
[thumbView release];
xPosition += (frame.size.width + THUMB_H_PADDING);
}
}
[thumbScrollView setContentSize:CGSizeMake(xPosition, scrollViewHeight)];
}
}
#pragma mark Utility methods
- (CGRect)zoomRectForScale:(float)scale withCenter:(CGPoint)center {
CGRect zoomRect;
// the zoom rect is in the content view's coordinates.
// At a zoom scale of 1.0, it would be the size of the imageScrollView's bounds.
// As the zoom scale decreases, so more content is visible, the size of the rect grows.
zoomRect.size.height = [imageScrollView frame].size.height / scale;
zoomRect.size.width = [imageScrollView frame].size.width / scale;
// choose an origin so as to get the right center.
zoomRect.origin.x = center.x - (zoomRect.size.width / 2.0);
zoomRect.origin.y = center.y - (zoomRect.size.height / 2.0);
return zoomRect;
}
#pragma mark -
#pragma mark Rotation support
// Ensure that the view controller supports rotation and that the split view can therefore show in both portrait and landscape.
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
return YES;
}
#end
If you've set up your UIScrollView in a Nib file, make sure it is resizing properly when rotated (Use the Autosizing controls in the Size Inspector in Interface Builder: Both sets of arrows inside the box should be red)
Then use this to rescale when the iPad changes orientation:
- (void)willAnimateSecondHalfOfRotationFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation duration:(NSTimeInterval)duration{
//if your UIImageView is called zoomView use this
CGRect zoomRect=CGRectMake(0,0,zoomView.frame.size.width, zoomView.frame.size.height);
[scrollView zoomToRect:zoomRect animated:YES];}
(Sorry about the bad placement of the {} but it wasn't pasting properly as code for some reason)
Hope this helps!

UIScrollView - (bounces = NO) seems to override (pagingEnabled = YES)

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.