content jumps on zooming out with UIScrollView - ios

I want help with my UIScrollView sample.
I created a simple program that scrolls and zooms the content (UIImageView). It works fine, except that the content frequently disappears to the right-bottom when I try zooming out. But since I set minimumZoomScale to 1.0f, it is actually not zooming out, only the content is jumping out of the view. And what is even more weird is that I cannot scroll up after this. Apparently content size is messed up as well.
The setup I have in my sample code is as in the figure below.
When I checked the status after (trying) zooming out, I found two wrong things.
_scrollView.contentSize is 480x360, which should not be smaller than 1000x1000
_scrollView.bounds jumped to the top somehow (i.e., _scrollView.bounds.origin.y is always 0)
To cope with the two items above, I added following code in my UIScrollViewDelegate and now it works fine.
- (void)scrollViewWillBeginZooming:(UIScrollView *)scrollView withView:(UIView *)view
{
if(scrollView == _scrollView && view == _contentView)
{
// Setting ivars for scrollViewDidZoom
_contentOffsetBeforeZoom = _scrollView.contentOffset;
_scrollViewBoundsBeforeZoom = _scrollView.bounds;
}
}
- (void)scrollViewDidZoom:(UIScrollView *)scrollView
{
if(scrollView == _scrollView)
{
// If you zoom out, there are cases where ScrollView content size becomes smaller than original,
// even though minimum zoom scale = 1. In that case, it will mess with the contentOffset as well.
if(_scrollView.contentSize.width < CONTENT_WIDTH || _scrollView.contentSize.height < CONTENT_HEIGHT)
{
_scrollView.contentSize = CGSizeMake(CONTENT_WIDTH, CONTENT_HEIGHT);
_scrollView.contentOffset = _contentOffsetBeforeZoom;
}
// If you zoom out, there are cases where ScrollView bounds goes outsize of contentSize rectangle.
if(_scrollView.bounds.origin.x + _scrollView.bounds.size.width > _scrollView.contentSize.width ||
_scrollView.bounds.origin.y + _scrollView.bounds.size.height > _scrollView.contentSize.height)
{
_scrollView.bounds = _scrollViewBoundsBeforeZoom;
}
}
}
However, does it need to come down to this? This is a very simple sequence, and it is hard to believe that Apple requires us to put this kind of effort. So, my bet is I am missing something here...
Following is my original code. Please help me find what I am doing wrong (or missing something)!
#define CONTENT_WIDTH 1000
#define CONTENT_HEIGHT 1000
>>>> Snip >>>>
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
_scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, 320, 460)];
_scrollView.contentSize = CGSizeMake(CONTENT_WIDTH, CONTENT_HEIGHT);
_scrollView.backgroundColor = [UIColor blueColor];
_scrollView.maximumZoomScale = 8.0f;
_scrollView.minimumZoomScale = 1.0f;
_scrollView.indicatorStyle = UIScrollViewIndicatorStyleWhite;
_scrollView.scrollEnabled = YES;
_scrollView.delegate = self;
[self.view addSubview:_scrollView];
_contentView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"sample.jpg"]]; // sample.jpg is 480x360
CGPoint center = (CGPoint){_scrollView.contentSize.width / 2, _scrollView.contentSize.height / 2};
_contentView.center = center;
[_scrollView addSubview:_contentView];
_scrollView.contentOffset = (CGPoint) {center.x - _scrollView.bounds.size.width / 2, center.y - _scrollView.bounds.size.height / 2};
}
- (UIView *) viewForZoomingInScrollView:(UIScrollView *)scrollView
{
if(scrollView == _scrollView)
{
return _contentView;
}
return nil;
}

I created a quick sample project and had the same issue you described using the code you pasted. I don't exactly know what the "proper" way to zoom is in iOS but I found this tutorial which says that you need to recenter your contentView after the scrollView has been zoomed. I would personally expect it to be automatically re-centered given that it is the view you're returning in the viewForZoomingInScrollView delegate method but apparently not.
- (void)centerScrollViewContents {
CGSize boundsSize = _scrollView.bounds.size;
CGRect contentsFrame = _contentView.frame;
if (contentsFrame.size.width < boundsSize.width) {
contentsFrame.origin.x = (boundsSize.width - contentsFrame.size.width) / 2.0f;
} else {
contentsFrame.origin.x = 0.0f;
}
if (contentsFrame.size.height < boundsSize.height) {
contentsFrame.origin.y = (boundsSize.height - contentsFrame.size.height) / 2.0f;
} else {
contentsFrame.origin.y = 0.0f;
}
_contentView.frame = contentsFrame;
}
- (void)scrollViewDidZoom:(UIScrollView *)scrollView {
// The scroll view has zoomed, so we need to re-center the contents
[self centerScrollViewContents];
}
The code above is not written by me but is simply copied from the tutorial. I think its pretty straightforward. Also, centring the contentView seems to be a lot more elegant then constantly changing the bounds and content size of the scrollview so give it a try.

If anyone is having an issue of bouncing when you zooming out resulting background to show, try removing bounces (Bounces Zoom) in Interface Builder.

I was able to fix this problem using the delegate answer that adjusted the rates after zoom... but then I remembered I was using auto-layout, and just adding constraints for centering horizontally and vertically (in addition to the constraints tying the image to each edge of the scroll view) solved the issue for me without using the delegate methods.

Olshansk answer in swift 5
func scrollViewDidZoom(_ scrollView: UIScrollView) {
centerScrollViewContents()
}
func centerScrollViewContents() {
let boundsSize = scrollView.bounds.size;
var contentsFrame = container.frame;
if (contentsFrame.size.width < boundsSize.width) {
contentsFrame.origin.x = (boundsSize.width - contentsFrame.size.width) / 2.0;
} else {
contentsFrame.origin.x = 0.0;
}
if (contentsFrame.size.height < boundsSize.height) {
contentsFrame.origin.y = (boundsSize.height - contentsFrame.size.height) / 2.0;
} else {
contentsFrame.origin.y = 0.0;
}
container.frame = contentsFrame;
}

Related

UIView inside a Scrollview zooming issue

I have a photo displaying iOS application in which the user should be able to view and zoom the image. As per the requirement the image should be displayed in entire scree(no transparent portion should be displayed and the image should fill the entire screen) and hence myself used AspectFill mode for UIImageView. In order to implement zooming functionality, used a UIScrollView and implemented viewForZoomingInScrollView delegate method of UIScrollView. The image view is connected to four edges of scroll view in StoryBoard and two constraints (imageHeight and imageWidth) are connected to IBOutlet. These constraints will be updated in ViewControllers viewDidLayoutSubviews method.
I have added the code below for your reference.
- (void)setImageViewModes {
self.scrollView.minimumZoomScale = CALCULATD_ZOOM_SCALE;
self.pageImageView.contentMode = UIViewContentModeScaleAspectFill;
}
pragma mark - UIScrollView Delegate
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
return MY_IMAGE_VIEW;
}
- (void)updateImageViewConstraints {
self.imageHeight.constant = CGRectGetHeight(self.scrollView.frame);
self.imageWidth.constant = CGRectGetWidth(self.scrollView.frame);
[self.view layoutIfNeeded];
}
However when I try to zoom out the image, it is rebounding to its initial state and outer portion of the image is clipped(It display only the initial filled portion of image). Is there is any workaround available to see the entire image on zoom out ?
I tried by setting minimum zoom scale for UIScrollView(I have a method in my code to calculate the minimum zoom scale of image) and the zoom functionality works. However when we zoom out, the UIIMage is always moves to top left corner of scrollview(Attached the screen shot for your reference).
I found a solution for this issue by Center content of UIScrollView when smaller (third answer) by subclassing UIScrollView and adjust image view frame in ‘layoutSubviews’ method of scroll view. This works fine while zoom out. But when we zoom and scroll the image to see the edge/side portion, scrollview readjust the image view to centre and I am unable to see the side portion of image in zoom in state. Any workaround for this issue ?
Use This
-(void)scrollViewDidZoom:(UIScrollView *)scrollView1{
[self centerScrollViewContent];
}
- (void)centerScrollViewContent {
CGSize boundsSize = self.scrollView.bounds.size;
CGRect contentsFrame = self.drawingView.frame;
if (contentsFrame.size.width < boundsSize.width) {
contentsFrame.origin.x = (boundsSize.width - contentsFrame.size.width) / 2.0f;
} else {
contentsFrame.origin.x = 0.0f;
}
if (contentsFrame.size.height < boundsSize.height) {
contentsFrame.origin.y = (boundsSize.height - contentsFrame.size.height) / 2.0f;
} else {
contentsFrame.origin.y = 0.0f;
}
self.drawingView.frame = contentsFrame;
}
Swift 3 version of Rahul's code
func scrollViewDidZoom(_ scrollView: UIScrollView) {
centerContentView()
}
func centerContentView() {
let boundsSize = scrollView.bounds.size
var contentFrame = contentView.frame
if contentFrame.size.width < boundsSize.width {
contentFrame.origin.x = (boundsSize.width - contentFrame.size.width) / 2.0
} else {
contentFrame.origin.x = 0.0
}
if contentFrame.size.height < boundsSize.height {
contentFrame.origin.y = (boundsSize.height - contentFrame.size.height) / 2.0
} else {
contentFrame.origin.y = 0.0
}
contentView.frame = contentFrame
}

UIScrollView zoom is not working properly

Whenever I click on an image, I want the image to display as a larger image with the ability to zoom. So far when I click on the image, it displays as I want it to inside a scrollview. However, I have to get lucky to be able to zoom in properly. Most of the time when I attempt to zoom, the image just moves down and to the right and does not zoom at all. Here is my code:
-(void) pictureButtonAction
{
self.scrollImageView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height)];
self.scrollImageView.contentSize = self.fullImageView.image.size;
self.scrollImageView.delegate = self;
self.scrollImageView.clipsToBounds = YES;
self.scrollImageView.scrollEnabled = YES;
[self.scrollImageView setMaximumZoomScale:4.0f];
[self.scrollImageView setMinimumZoomScale:1.0f];
[self.view addSubview:self.scrollImageView];
[self.scrollImageView addSubview:fullImageView];
}
-(UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
{
return self.fullImageView;
}
ImageViews frame got changed while zooming , so that it moves down. So, You have to centralize the image view each time you zoom .
your scrollviews contentsize should be greater than or equal to your image size , if your fullImageView.image.size is less than your scrollviews bounds ,then set your scrollviews contentSize atleast double the scrollviews bounds .
call the below function in scrollViewDidZoom delegate method
-(void) centerScrollViewContents
{
CGSize boundsSize = self.scrollView.bounds.size;
CGRect contentsFrame = self.imageView.frame;
if (contentsFrame.size.width < boundsSize.width) {
contentsFrame.origin.x = (boundsSize.width - contentsFrame.size.width) / 2.0f;
} else {
contentsFrame.origin.x = 0.0f;
}
if (contentsFrame.size.height < boundsSize.height) {
contentsFrame.origin.y = (boundsSize.height - contentsFrame.size.height) / 2.0f;
} else {
contentsFrame.origin.y = 0.0f;
}
self.imageView.frame = contentsFrame;
}
Try this , hope it will help you ; happy coding ! :)
#Maria's answer works but when zooming you'll experience unwanted bounces, instead of using it under scrollViewDidZoom, use scrollViewDidEndZooming: delegate to prevent that..
- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(CGFloat)scale
{
[self centerScrollViewContents];
}
and with a little enhancement from her code:
[UIView animateWithDuration:0 animations:^{
self.previewImageView.frame = contentsFrame;
}];
a bit late for an answer but i hope this will be useful to some..

UIKit Dynamics: Attachment inside UITableViewCell

My table view cells contain a circle in an UIView, indicating a value. I want to add the UIKit Dynamics attachment behaviour to that circle in order to for it to lag a bit when scrolling.
I don't want to attach the individual cells to each other but only the circle view to the UITableViewCell. The rest of the cell should scroll as usual.
Problem: The UITableViewCell has its origin always at (0, 0). How can I add the circle to a view that actually does move when scrolling?
I finally got it to work. The UITableView moves the coordinate system of every cell and of all views contained within that cell. Therefor I needed to manually move my view inside the UITableViewCell during scrolling while still referring to the initial anchor point.
The table view controller:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
BOOL scrollingUp = '\0';
if (self.lastContentOffset > scrollView.contentOffset.y) {
scrollingUp = YES;
}
else if (self.lastContentOffset < scrollView.contentOffset.y) {
scrollingUp = NO;
}
NSInteger offset = 64; // To compensate for the navigation bar.
if (scrollingUp) {
offset = offset - scrollView.contentOffset.y;
}
else {
offset = offset + scrollView.contentOffset.y;
}
// Limit the offset so the views will not disappear during fast scrolling.
if (offset > 10) {
offset = 10;
}
else if (offset < -10) {
offset = -10;
}
// lastContentOffset is an instance variable.
self.lastContentOffset = scrollView.contentOffset.y;
for (UITableViewCell *cell in self.tableView.visibleCells) {
// Use CoreAnimation to prohibit flicker.
[UIView beginAnimations:#"Display notification" context:nil];
[UIView setAnimationDuration:0.5f];
[UIView setAnimationBeginsFromCurrentState:YES];
cell.view.frame = CGRectMake(cell.view.frame.origin.x, offset, cell.view.frame.size.width, cell.view.frame.size.height);
[UIView commitAnimations];
[cell.dynamicAnimator updateItemUsingCurrentState:cell.view];
}
}
The table view cell:
-(void)layoutSubviews {
[super layoutSubviews];
// _view is the animated UIView.
UIDynamicItemBehavior *viewBehavior = [[UIDynamicItemBehavior alloc] initWithItems:#[_view]];
viewBehavior.elasticity = 0.9f;
UIAttachmentBehavior *attachmentBehaviorView = [[UIAttachmentBehavior alloc] initWithItem:_view attachedToAnchor:CGPointMake(_anchorView.frame.origin.x + _anchorView.frame.size.width / 2.0f, _anchorView.frame.origin.y + _anchorView.frame.size.height / 2.0f)];
attachmentBehaviorView.damping = 8.0f;
attachmentBehaviorView.frequency = 4.0f;
attachmentBehaviorView.length = 0.0f;
[_dynamicAnimator addBehavior:viewBehavior];
[_dynamicAnimator addBehavior:attachmentBehaviorView];
}
You can change the anchorPoint of UIAttachmentBehavior during -[scrollViewDidScroll:]. You may refer to the following code snippet:
- (void)viewDidLoad
{
UIDynamicAnimator *animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];
UIAttachmentBehavior *behavior1 = [[UIAttachmentBehavior alloc] initWithItem:self.circleView
attachedToAnchor:[self tableViewAnchor]];
behavior1.length = 10.0;
behavior1.damping = 0.3;
behavior1.frequency = 2.5;
[animator addBehavior:behavior1];
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
behavior1.anchorPoint = [self.tableView convertPoint:[self tableViewAnchor] toView:self.view];
}
- (CGPoint)tableViewAnchor
{
return CGPointMake(160.0, 154.0); // return your target coordination w.r.t. the table view
}
Preview:

IOS ScrollView Issues

Hey I'm following this tutorial and currently on the first step but am experiencing some weird behavior. The tutorial is on creating scroll views for images and ends with showing how to create a paged horizontal ScrollView with a page controller. I followed the first section and things seemed to be working fine. However when I ran the code it did not perform properly, after about an hour of debugging I downloaded the final project, ran it on my phone to check if it was working correctly (it was) then copied the code into the required files in my project. My project still wouldn't run. So I figured it must be the way in which my story board was configured on the project setup. I then oped the working example cleared their storyboard and created the views and segues my self, in the exact same way and order I had done in my project. Bam! it still worked, when I deleted all views in my story board and recreated them in the exact same fashion, still nothing. Could someone look into this tutorial and tell me if I'm going crazy or they experience similar results. I have narrowed it down to two possibilities, the project provided at the end of the tutorial was created differently and the delegates or outlets were hooked up in a way that only works with the project, or the project was created with an earlier version of the iPhone development kit that works with the current version of XCode but not when I create a new project. Then again I am fairly new to this so I will throw up the code I'm using and hope for the best.
ViewController.h file:
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController <UIScrollViewDelegate>
#property (nonatomic, strong) IBOutlet UIScrollView *scrollView;
#end
ViewController.m file:
#import "ViewController.h"
#interface ViewController ()
#property (nonatomic, strong) UIImageView *imageView;
- (void)centerScrollViewContents;
- (void)scrollViewDoubleTapped:(UITapGestureRecognizer*)recognizer;
- (void)scrollViewTwoFingerTapped:(UITapGestureRecognizer*)recognizer;
#end
#implementation ViewController
#synthesize scrollView = _scrollView;
#synthesize imageView = _imageView;
- (void)centerScrollViewContents {
CGSize boundsSize = self.scrollView.bounds.size;
CGRect contentsFrame = self.imageView.frame;
if (contentsFrame.size.width < boundsSize.width) {
contentsFrame.origin.x = (boundsSize.width - contentsFrame.size.width) / 2.0f;
} else {
contentsFrame.origin.x = 0.0f;
}
if (contentsFrame.size.height < boundsSize.height) {
contentsFrame.origin.y = (boundsSize.height - contentsFrame.size.height) / 2.0f;
} else {
contentsFrame.origin.y = 0.0f;
}
self.imageView.frame = contentsFrame;
}
- (void)scrollViewDoubleTapped:(UITapGestureRecognizer*)recognizer {
// Get the location within the image view where we tapped
CGPoint pointInView = [recognizer locationInView:self.imageView];
// Get a zoom scale that's zoomed in slightly, capped at the maximum zoom scale specified by the scroll view
CGFloat newZoomScale = self.scrollView.zoomScale * 1.5f;
newZoomScale = MIN(newZoomScale, self.scrollView.maximumZoomScale);
// Figure out the rect we want to zoom to, then zoom to it
CGSize scrollViewSize = self.scrollView.bounds.size;
CGFloat w = scrollViewSize.width / newZoomScale;
CGFloat h = scrollViewSize.height / newZoomScale;
CGFloat x = pointInView.x - (w / 2.0f);
CGFloat y = pointInView.y - (h / 2.0f);
CGRect rectToZoomTo = CGRectMake(x, y, w, h);
[self.scrollView zoomToRect:rectToZoomTo animated:YES];
}
- (void)scrollViewTwoFingerTapped:(UITapGestureRecognizer*)recognizer {
// Zoom out slightly, capping at the minimum zoom scale specified by the scroll view
CGFloat newZoomScale = self.scrollView.zoomScale / 1.5f;
newZoomScale = MAX(newZoomScale, self.scrollView.minimumZoomScale);
[self.scrollView setZoomScale:newZoomScale animated:YES];
}
- (void)viewDidLoad {
[super viewDidLoad];
// Set a nice title
self.title = #"Image";
// Set up the image we want to scroll & zoom and add it to the scroll view
UIImage *image = [UIImage imageNamed:#"photo1.png"];
self.imageView = [[UIImageView alloc] initWithImage:image];
self.imageView.frame = (CGRect){.origin=CGPointMake(0.0f, 0.0f), .size=image.size};
[self.scrollView addSubview:self.imageView];
// Tell the scroll view the size of the contents
self.scrollView.contentSize = image.size;
UITapGestureRecognizer *doubleTapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(scrollViewDoubleTapped:)];
doubleTapRecognizer.numberOfTapsRequired = 2;
doubleTapRecognizer.numberOfTouchesRequired = 1;
[self.scrollView addGestureRecognizer:doubleTapRecognizer];
UITapGestureRecognizer *twoFingerTapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(scrollViewTwoFingerTapped:)];
twoFingerTapRecognizer.numberOfTapsRequired = 1;
twoFingerTapRecognizer.numberOfTouchesRequired = 2;
[self.scrollView addGestureRecognizer:twoFingerTapRecognizer];
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
// Set up the minimum & maximum zoom scales
CGRect scrollViewFrame = self.scrollView.frame;
CGFloat scaleWidth = scrollViewFrame.size.width / self.scrollView.contentSize.width;
CGFloat scaleHeight = scrollViewFrame.size.height / self.scrollView.contentSize.height;
CGFloat minScale = MIN(scaleWidth, scaleHeight);
self.scrollView.minimumZoomScale = minScale;
self.scrollView.maximumZoomScale = 1.0f;
self.scrollView.zoomScale = minScale;
[self centerScrollViewContents];
}
- (void)viewDidUnload
{
[super viewDidUnload];
// Release any retained subviews of the main view.
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
}
- (UIView*)viewForZoomingInScrollView:(UIScrollView *)scrollView
{
// Return the view that we want to zoom
return self.imageView;
}
- (void)scrollViewDidZoom:(UIScrollView *)scrollView
{
// The scroll view has zoomed, so we need to re-center the contents
[self centerScrollViewContents];
}
#end
The above code doesn't work as it does in the tutorials project. The following UIScrollView delegate method viewForZoomingInScrollView produces this error:
2013-05-14 10:01:18.585 TestScrollView[3809:907] Made it
May 14 10:01:18 Scott-Laroses-iPhone TestScrollView[3809] : CGAffineTransformInvert: singular matrix.
May 14 10:01:18 Scott-Laroses-iPhone TestScrollView[3809] : CGAffineTransformInvert: singular matrix.
which makes me think the delate isn't properly set up despite me doing it exactly the same as in the tutorial and programmatically when that didn't work. Any ideas?
Your problem may be from the zoomScale and minimumZoomScale if the value assigned is 0. Make sure that the value is never 0 for those two properties.
I figured it out! The issues was that the auto layout constraints were messing up the off screen views. So I disabled auto layout and it worked fine.

Center content of UIScrollView when smaller

I have a UIImageView inside a UIScrollView which I use for zooming and scrolling. If the image / content of the scroll view is bigger than the scroll view, everything works fine. However, when the image becomes smaller than the scroll view, it sticks to the top left corner of the scroll view. I would like to keep it centered, like the Photos app.
Any ideas or examples about keeping the content of the UIScrollView centered when it's smaller?
I am working with iPhone 3.0.
The following code almost works. The image returns to the top left corner if I pinch it after reaching the minimum zoom level.
- (void)loadView {
[super loadView];
// set up main scroll view
imageScrollView = [[UIScrollView alloc] initWithFrame:[[self view] bounds]];
[imageScrollView setBackgroundColor:[UIColor blackColor]];
[imageScrollView setDelegate:self];
[imageScrollView setBouncesZoom:YES];
[[self view] addSubview:imageScrollView];
UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"WeCanDoIt.png"]];
[imageView setTag:ZOOM_VIEW_TAG];
[imageScrollView setContentSize:[imageView frame].size];
[imageScrollView addSubview:imageView];
CGSize imageSize = imageView.image.size;
[imageView release];
CGSize maxSize = imageScrollView.frame.size;
CGFloat widthRatio = maxSize.width / imageSize.width;
CGFloat heightRatio = maxSize.height / imageSize.height;
CGFloat initialZoom = (widthRatio > heightRatio) ? heightRatio : widthRatio;
[imageScrollView setMinimumZoomScale:initialZoom];
[imageScrollView setZoomScale:1];
float topInset = (maxSize.height - imageSize.height) / 2.0;
float sideInset = (maxSize.width - imageSize.width) / 2.0;
if (topInset < 0.0) topInset = 0.0;
if (sideInset < 0.0) sideInset = 0.0;
[imageScrollView setContentInset:UIEdgeInsetsMake(topInset, sideInset, -topInset, -sideInset)];
}
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
return [imageScrollView viewWithTag:ZOOM_VIEW_TAG];
}
/************************************** 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];
// END Bug workaround
CGSize maxSize = imageScrollView.frame.size;
CGSize viewSize = view.frame.size;
float topInset = (maxSize.height - viewSize.height) / 2.0;
float sideInset = (maxSize.width - viewSize.width) / 2.0;
if (topInset < 0.0) topInset = 0.0;
if (sideInset < 0.0) sideInset = 0.0;
[imageScrollView setContentInset:UIEdgeInsetsMake(topInset, sideInset, -topInset, -sideInset)];
}
I've got very simple solution!
All you need is to update the center of your subview (imageview) while zooming in the ScrollViewDelegate.
If zoomed image is smaller than scrollview then adjust subview.center else center is (0,0).
- (void)scrollViewDidZoom:(UIScrollView *)scrollView
{
UIView *subView = [scrollView.subviews objectAtIndex:0];
CGFloat offsetX = MAX((scrollView.bounds.size.width - scrollView.contentSize.width) * 0.5, 0.0);
CGFloat offsetY = MAX((scrollView.bounds.size.height - scrollView.contentSize.height) * 0.5, 0.0);
subView.center = CGPointMake(scrollView.contentSize.width * 0.5 + offsetX,
scrollView.contentSize.height * 0.5 + offsetY);
}
#EvelynCordner's answer was the one that worked best in my app. A lot less code than the other options too.
Here's the Swift version if anyone needs it:
func scrollViewDidZoom(_ scrollView: UIScrollView) {
let offsetX = max((scrollView.bounds.width - scrollView.contentSize.width) * 0.5, 0)
let offsetY = max((scrollView.bounds.height - scrollView.contentSize.height) * 0.5, 0)
scrollView.contentInset = UIEdgeInsets(top: offsetY, left: offsetX, bottom: 0, right: 0)
}
Okay, I've been fighting this for the past two days on and off and having finally come to a pretty reliable (so far...) solution I thought I should share it and save others some pain. :) If you do find a problem with this solution please shout!
I've basically gone through what everyone else has: searching StackOverflow, the Apple Developer Forums, looked at the code for three20, ScrollingMadness, ScrollTestSuite, etc. I've tried enlarging the UIImageView frame, playing with the UIScrollView's offset and/or insets from the ViewController, etc. but nothing worked great (as everyone else has found out too).
After sleeping on it, I tried a couple of alternative angles:
Subclassing the UIImageView so it alters it's own size dynamically - this didn't work well at all.
Subclassing the UIScrollView so it alters it's own contentOffset dynamically - this is the one that seems to be a winner for me.
With this subclassing UIScrollView method I'm overriding the contentOffset mutator so it isn't setting {0,0} when the image is scaled smaller than the viewport - instead it's setting the offset such that the image will be kept centred in the viewport. So far, it always seems to work. I've checked it with wide, tall, tiny & large images and doesn't have the "works but pinch at minimum zoom breaks it" issue.
I've uploaded an example project to github that uses this solution, you can find it here: http://github.com/nyoron/NYOBetterZoom
For a solution better suited for scroll views that use autolayout, use content insets of the scroll view rather than updating the frames of your scroll view's subviews.
- (void)scrollViewDidZoom:(UIScrollView *)scrollView
{
CGFloat offsetX = MAX((scrollView.bounds.size.width - scrollView.contentSize.width) * 0.5, 0.0);
CGFloat offsetY = MAX((scrollView.bounds.size.height - scrollView.contentSize.height) * 0.5, 0.0);
self.scrollView.contentInset = UIEdgeInsetsMake(offsetY, offsetX, 0.f, 0.f);
}
This code should work on most versions of iOS (and has been tested to work on 3.1 upwards).
It's based on the Apple WWDC code for the photoscoller.
Add the below to your subclass of UIScrollView, and replace tileContainerView with the view containing your image or tiles:
- (void)layoutSubviews {
[super layoutSubviews];
// center the image as it becomes smaller than the size of the screen
CGSize boundsSize = self.bounds.size;
CGRect frameToCenter = tileContainerView.frame;
// center horizontally
if (frameToCenter.size.width < boundsSize.width)
frameToCenter.origin.x = (boundsSize.width - frameToCenter.size.width) / 2;
else
frameToCenter.origin.x = 0;
// center vertically
if (frameToCenter.size.height < boundsSize.height)
frameToCenter.origin.y = (boundsSize.height - frameToCenter.size.height) / 2;
else
frameToCenter.origin.y = 0;
tileContainerView.frame = frameToCenter;
}
Currently I'm subclassing UIScrollView and overriding setContentOffset: to adjust the offset based on contentSize. It works both with pinch and programatic zooming.
#implementation HPCenteringScrollView
- (void)setContentOffset:(CGPoint)contentOffset
{
const CGSize contentSize = self.contentSize;
const CGSize scrollViewSize = self.bounds.size;
if (contentSize.width < scrollViewSize.width)
{
contentOffset.x = -(scrollViewSize.width - contentSize.width) / 2.0;
}
if (contentSize.height < scrollViewSize.height)
{
contentOffset.y = -(scrollViewSize.height - contentSize.height) / 2.0;
}
[super setContentOffset:contentOffset];
}
#end
In addition to being short and sweet, this code produces a much smoother zoom than #Erdemus solution. You can see it in action in the RMGallery demo.
I've spent a day fighting with this issue, and ended up implementing the scrollViewDidEndZooming:withView:atScale: as follows:
- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale {
CGFloat screenWidth = [[UIScreen mainScreen] bounds].size.width;
CGFloat screenHeight = [[UIScreen mainScreen] bounds].size.height;
CGFloat viewWidth = view.frame.size.width;
CGFloat viewHeight = view.frame.size.height;
CGFloat x = 0;
CGFloat y = 0;
if(viewWidth < screenWidth) {
x = screenWidth / 2;
}
if(viewHeight < screenHeight) {
y = screenHeight / 2 ;
}
self.scrollView.contentInset = UIEdgeInsetsMake(y, x, y, x);
}
This ensures that when the image is smaller than the screen, there's still adequate space around it so you can position it to the exact place you want.
(assuming that your UIScrollView contains an UIImageView to hold the image)
Essentially, what this does is check whether your image view's width / height is smaller that the screen's width / height, and if so, create an inset of half the screen's width / height (you could probably make this larger if you want the image to go out of the screen bounds).
Note that since this is a UIScrollViewDelegate method, don't forget to add it to your view controller's declaration, so to avoid getting a build issue.
If contentInset is not needed for anything else, it can be used to center scrollview's content.
class ContentCenteringScrollView: UIScrollView {
override var bounds: CGRect {
didSet { updateContentInset() }
}
override var contentSize: CGSize {
didSet { updateContentInset() }
}
private func updateContentInset() {
var top = CGFloat(0)
var left = CGFloat(0)
if contentSize.width < bounds.width {
left = (bounds.width - contentSize.width) / 2
}
if contentSize.height < bounds.height {
top = (bounds.height - contentSize.height) / 2
}
contentInset = UIEdgeInsets(top: top, left: left, bottom: top, right: left)
}
}
Advantage if this approach is that you can still use contentLayoutGuide to place content inside scrollview
scrollView.addSubview(imageView)
imageView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
imageView.leadingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.leadingAnchor),
imageView.trailingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.trailingAnchor),
imageView.topAnchor.constraint(equalTo: scrollView.contentLayoutGuide.topAnchor),
imageView.bottomAnchor.constraint(equalTo: scrollView.contentLayoutGuide.bottomAnchor)
])
or just drag and drop the content in Xcode's Interface Builder.
Apple has released the 2010 WWDC session videos to all members of the iphone developer program. One of the topics discussed is how they created the photos app!!! They build a very similar app step by step and have made all the code available for free.
It does not use private api either. I can't put any of the code here because of the non disclosure agreement, but here is a link to the sample code download. You will probably need to login to gain access.
http://connect.apple.com/cgi-bin/WebObjects/MemberSite.woa/wa/getSoftware?code=y&source=x&bundleID=20645
And, here is a link to the iTunes WWDC page:
http://insideapple.apple.com/redir/cbx-cgi.do?v=2&la=en&lc=&a=kGSol9sgPHP%2BtlWtLp%2BEP%2FnxnZarjWJglPBZRHd3oDbACudP51JNGS8KlsFgxZto9X%2BTsnqSbeUSWX0doe%2Fzv%2FN5XV55%2FomsyfRgFBysOnIVggO%2Fn2p%2BiweDK%2F%2FmsIXj
Ok, this solution is working for me. I have a subclass of UIScrollView with a reference to the UIImageView it is displaying. Whenever the UIScrollView zooms, the contentSize property is adjusted. It is in the setter that I scale the UIImageView appropriately and also adjust its center position.
-(void) setContentSize:(CGSize) size{
CGSize lSelfSize = self.frame.size;
CGPoint mid;
if(self.zoomScale >= self.minimumZoomScale){
CGSize lImageSize = cachedImageView.initialSize;
float newHeight = lImageSize.height * self.zoomScale;
if (newHeight < lSelfSize.height ) {
newHeight = lSelfSize.height;
}
size.height = newHeight;
float newWidth = lImageSize.width * self.zoomScale;
if (newWidth < lSelfSize.width ) {
newWidth = lSelfSize.width;
}
size.width = newWidth;
mid = CGPointMake(size.width/2, size.height/2);
}
else {
mid = CGPointMake(lSelfSize.width/2, lSelfSize.height/2);
}
cachedImageView.center = mid;
[super setContentSize:size];
[self printLocations];
NSLog(#"zoom %f setting size %f x %f",self.zoomScale,size.width,size.height);
}
Evertime I set the image on the UIScrollView I resize it. The UIScrollView in the scrollview is also a custom class I created.
-(void) resetSize{
if (!scrollView){//scroll view is view containing imageview
return;
}
CGSize lSize = scrollView.frame.size;
CGSize lSelfSize = self.image.size;
float lWidth = lSize.width/lSelfSize.width;
float lHeight = lSize.height/lSelfSize.height;
// choose minimum scale so image width fits screen
float factor = (lWidth<lHeight)?lWidth:lHeight;
initialSize.height = lSelfSize.height * factor;
initialSize.width = lSelfSize.width * factor;
[scrollView setContentSize:lSize];
[scrollView setContentOffset:CGPointZero];
scrollView.userInteractionEnabled = YES;
}
With these two methods I am able to have a view that behaves just like the photos app.
The way I've done this is to add an extra view into the hierarchy:
UIScrollView -> UIView -> UIImageView
Give your UIView the same aspect ratio as your UIScrollView, and centre your UIImageView into that.
Just the approved answer in swift, but without subclassing using the delegate
func centerScrollViewContents(scrollView: UIScrollView) {
let contentSize = scrollView.contentSize
let scrollViewSize = scrollView.frame.size;
var contentOffset = scrollView.contentOffset;
if (contentSize.width < scrollViewSize.width) {
contentOffset.x = -(scrollViewSize.width - contentSize.width) / 2.0
}
if (contentSize.height < scrollViewSize.height) {
contentOffset.y = -(scrollViewSize.height - contentSize.height) / 2.0
}
scrollView.setContentOffset(contentOffset, animated: false)
}
// UIScrollViewDelegate
func scrollViewDidZoom(scrollView: UIScrollView) {
centerScrollViewContents(scrollView)
}
I know some answers above are right, but I just want to give my answer with some explanation, the comments will make you understand why we do like this.
When I load the scrollView for the first time, I write the following code to make it center, please notice we set contentOffset first, then contentInset
scrollView.maximumZoomScale = 8
scrollView.minimumZoomScale = 1
// set vContent frame
vContent.frame = CGRect(x: 0,
y: 0 ,
width: vContentWidth,
height: vContentWidth)
// set scrollView.contentSize
scrollView.contentSize = vContent.frame.size
//on the X direction, if contentSize.width > scrollView.bounds.with, move scrollView from 0 to offsetX to make it center(using `scrollView.contentOffset`)
// if not, don't need to set offset, but we need to set contentInset to make it center.(using `scrollView.contentInset`)
// so does the Y direction.
let offsetX = max((scrollView.contentSize.width - scrollView.bounds.width) * 0.5, 0)
let offsetY = max((scrollView.contentSize.height - scrollView.bounds.height) * 0.5, 0)
scrollView.contentOffset = CGPoint(x: offsetX, y: offsetY)
let topX = max((scrollView.bounds.width - scrollView.contentSize.width) * 0.5, 0)
let topY = max((scrollView.bounds.height - scrollView.contentSize.height) * 0.5, 0)
scrollView.contentInset = UIEdgeInsets(top: topY, left: topX, bottom: 0, right: 0)
Then, when I pinch vContent, I write the following code to make it center.
func scrollViewDidZoom(_ scrollView: UIScrollView) {
//we just need to ensure that the content is in the center when the contentSize is less than scrollView.size.
let topX = max((scrollView.bounds.width - scrollView.contentSize.width) * 0.5, 0)
let topY = max((scrollView.bounds.height - scrollView.contentSize.height) * 0.5, 0)
scrollView.contentInset = UIEdgeInsets(top: topY, left: topX, bottom: 0, right: 0)
}
You could watch the contentSize property of the UIScrollView (using key-value observing or similar), and automatically adjust the contentInset whenever the contentSize changes to be less than the size of the scroll view.
One elegant way to center the content of UISCrollView is this.
Add one observer to the contentSize of your UIScrollView, so this method will be called everytime the content change...
[myScrollView addObserver:delegate
forKeyPath:#"contentSize"
options:(NSKeyValueObservingOptionNew)
context:NULL];
Now on your observer method:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
// Correct Object Class.
UIScrollView *pointer = object;
// Calculate Center.
CGFloat topCorrect = ([pointer bounds].size.height - [pointer viewWithTag:100].bounds.size.height * [pointer zoomScale]) / 2.0 ;
topCorrect = ( topCorrect < 0.0 ? 0.0 : topCorrect );
topCorrect = topCorrect - ( pointer.frame.origin.y - imageGallery.frame.origin.y );
// Apply Correct Center.
pointer.center = CGPointMake(pointer.center.x,
pointer.center.y + topCorrect ); }
You should change the [pointer
viewWithTag:100]. Replace by your
content view UIView.
Also change imageGallery pointing to your window size.
This will correct the center of the content everytime his size change.
NOTE: The only way this content don't works very well is with standard zoom functionality of the UIScrollView.
This is my solution to that problem which works pretty fine for any kind of view inside a scrollview.
-(void)scrollViewDidZoom:(__unused UIScrollView *)scrollView
{
CGFloat top;
CGFloat left;
CGFloat bottom;
CGFloat right;
if (_scrollView.contentSize.width < scrollView.bounds.size.width) {
DDLogInfo(#"contentSize %#",NSStringFromCGSize(_scrollView.contentSize));
CGFloat width = (_scrollView.bounds.size.width-_scrollView.contentSize.width)/2.0;
left = width;
right = width;
}else {
left = kInset;
right = kInset;
}
if (_scrollView.contentSize.height < scrollView.bounds.size.height) {
CGFloat height = (_scrollView.bounds.size.height-_scrollView.contentSize.height)/2.0;
top = height;
bottom = height;
}else {
top = kInset;
right = kInset;
}
_scrollView.contentInset = UIEdgeInsetsMake(top, left, bottom, right);
if ([self.tiledScrollViewDelegate respondsToSelector:#selector(tiledScrollViewDidZoom:)])
{
[self.tiledScrollViewDelegate tiledScrollViewDidZoom:self];
}
}
There are a plenty of solutions here, but I'd risk putting here my own. It's good for two reasons: it doesn't mess zooming experience, as would do updating image view frame in progress, and also it respects original scroll view insets (say, defined in xib or storyboard for graceful handling of semi-transparent toolbars etc).
First, define a small helper:
CGSize CGSizeWithAspectFit(CGSize containerSize, CGSize contentSize) {
CGFloat containerAspect = containerSize.width / containerSize.height,
contentAspect = contentSize.width / contentSize.height;
CGFloat scale = containerAspect > contentAspect
? containerSize.height / contentSize.height
: containerSize.width / contentSize.width;
return CGSizeMake(contentSize.width * scale, contentSize.height * scale);
}
To retain original insets, defined field:
UIEdgeInsets originalScrollViewInsets;
And somewhere in viewDidLoad fill it:
originalScrollViewInsets = self.scrollView.contentInset;
To place UIImageView into UIScrollView (assuming UIImage itself is in loadedImage var):
CGSize containerSize = self.scrollView.bounds.size;
containerSize.height -= originalScrollViewInsets.top + originalScrollViewInsets.bottom;
containerSize.width -= originalScrollViewInsets.left + originalScrollViewInsets.right;
CGSize contentSize = CGSizeWithAspectFit(containerSize, loadedImage.size);
UIImageView *imageView = [[UIImageView alloc] initWithFrame:(CGRect) { CGPointZero, contentSize }];
imageView.autoresizingMask = UIViewAutoresizingNone;
imageView.contentMode = UIViewContentModeScaleAspectFit;
imageView.image = loadedImage;
[self.scrollView addSubview:imageView];
self.scrollView.contentSize = contentSize;
[self centerImageViewInScrollView];
scrollViewDidZoom: from UIScrollViewDelegate for that scroll view:
- (void)scrollViewDidZoom:(UIScrollView *)scrollView {
if (scrollView == self.scrollView) {
[self centerImageViewInScrollView];
}
}
An finally, centering itself:
- (void)centerImageViewInScrollView {
CGFloat excessiveWidth = MAX(0.0, self.scrollView.bounds.size.width - self.scrollView.contentSize.width),
excessiveHeight = MAX(0.0, self.scrollView.bounds.size.height - self.scrollView.contentSize.height),
insetX = excessiveWidth / 2.0,
insetY = excessiveHeight / 2.0;
self.scrollView.contentInset = UIEdgeInsetsMake(
MAX(insetY, originalScrollViewInsets.top),
MAX(insetX, originalScrollViewInsets.left),
MAX(insetY, originalScrollViewInsets.bottom),
MAX(insetX, originalScrollViewInsets.right)
);
}
I didn't test orientation change yet (i.e. proper reaction for resizing UIScrollView itself), but fix for that should be relatively easy.
You'll find that the solution posted by Erdemus does work, but… There are some cases where the scrollViewDidZoom method does not get invoked & your image is stuck to the top left corner. A simple solution is to explicitly invoke the method when you initially display an image, like this:
[self scrollViewDidZoom: scrollView];
In many cases, you may be invoking this method twice, but this is a cleaner solution than some of the other answers in this topic.
Apple's Photo Scroller Example does exactly what you are looking for. Put this in your UIScrollView Subclass and change _zoomView to be your UIImageView.
-(void)layoutSubviews{
[super layoutSubviews];
// center the zoom view as it becomes smaller than the size of the screen
CGSize boundsSize = self.bounds.size;
CGRect frameToCenter = self.imageView.frame;
// center horizontally
if (frameToCenter.size.width < boundsSize.width){
frameToCenter.origin.x = (boundsSize.width - frameToCenter.size.width) / 2;
}else{
frameToCenter.origin.x = 0;
}
// center vertically
if (frameToCenter.size.height < boundsSize.height){
frameToCenter.origin.y = (boundsSize.height - frameToCenter.size.height) / 2;
}else{
frameToCenter.origin.y = 0;
}
self.imageView.frame = frameToCenter;
}
Apple's Photo Scroller Sample Code
To make the animation flow nicely, set
self.scrollview.bouncesZoom = NO;
and use this function (finding the center using the method at this answer)
- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(CGFloat)scale {
[UIView animateWithDuration:0.2 animations:^{
float offsetX = MAX((scrollView.bounds.size.width-scrollView.contentSize.width)/2, 0);
float offsetY = MAX((scrollView.bounds.size.height-scrollView.contentSize.height)/2, 0);
self.imageCoverView.center = CGPointMake(scrollView.contentSize.width*0.5+offsetX, scrollView.contentSize.height*0.5+offsetY);
}];
}
This creates the bouncing effect but doesn't involve any sudden movements beforehand.
In case your inner imageView has initial specific width(eg 300) and you just want to center its width only on zoom smaller than its initial width this might help you also.
func scrollViewDidZoom(scrollView: UIScrollView){
if imageView.frame.size.width < 300{
imageView.center.x = self.view.frame.width/2
}
}
Here's the current way I'm making this work. It's better but still not perfect. Try setting:
myScrollView.bouncesZoom = YES;
to fix the problem with the view not centering when at minZoomScale.
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
CGSize screenSize = [[self view] bounds].size;//[[UIScreen mainScreen] bounds].size;//
CGSize photoSize = [yourImage size];
CGFloat topInset = (screenSize.height - photoSize.height * [myScrollView zoomScale]) / 2.0;
CGFloat sideInset = (screenSize.width - photoSize.width * [myScrollView zoomScale]) / 2.0;
if (topInset < 0.0)
{ topInset = 0.0; }
if (sideInset < 0.0)
{ sideInset = 0.0; }
[myScrollView setContentInset:UIEdgeInsetsMake(topInset, sideInset, -topInset, -sideInset)];
ApplicationDelegate *appDelegate = (ApplicationDelegate *)[[UIApplication sharedApplication] delegate];
CGFloat scrollViewHeight; //Used later to calculate the height of the scrollView
if (appDelegate.navigationController.navigationBar.hidden == YES) //If the NavBar is Hidden, set scrollViewHeight to 480
{ scrollViewHeight = 480; }
if (appDelegate.navigationController.navigationBar.hidden == NO) //If the NavBar not Hidden, set scrollViewHeight to 360
{ scrollViewHeight = 368; }
imageView.frame = CGRectMake(0, 0, CGImageGetWidth(yourImage)* [myScrollView zoomScale], CGImageGetHeight(yourImage)* [myScrollView zoomScale]);
[imageView setContentMode:UIViewContentModeCenter];
}
Also, I do the following to prevent the image from sticking a the side after zooming out.
- (void) scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale {
myScrollView.frame = CGRectMake(0, 0, 320, 420);
//put the correct parameters for your scroll view width and height above
}
Okay, I think I've found a pretty good solution to this problem. The trick is to constantly readjust the imageView's frame. I find this works much better than constantly adjusting the contentInsets or contentOffSets. I had to add a bit of extra code to accommodate both portrait and landscape images.
Here's the code:
- (void) scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale {
CGSize screenSize = [[self view] bounds].size;
if (myScrollView.zoomScale <= initialZoom +0.01) //This resolves a problem with the code not working correctly when zooming all the way out.
{
imageView.frame = [[self view] bounds];
[myScrollView setZoomScale:myScrollView.zoomScale +0.01];
}
if (myScrollView.zoomScale > initialZoom)
{
if (CGImageGetWidth(temporaryImage.CGImage) > CGImageGetHeight(temporaryImage.CGImage)) //If the image is wider than tall, do the following...
{
if (screenSize.height >= CGImageGetHeight(temporaryImage.CGImage) * [myScrollView zoomScale]) //If the height of the screen is greater than the zoomed height of the image do the following...
{
imageView.frame = CGRectMake(0, 0, 320*(myScrollView.zoomScale), 368);
}
if (screenSize.height < CGImageGetHeight(temporaryImage.CGImage) * [myScrollView zoomScale]) //If the height of the screen is less than the zoomed height of the image do the following...
{
imageView.frame = CGRectMake(0, 0, 320*(myScrollView.zoomScale), CGImageGetHeight(temporaryImage.CGImage) * [myScrollView zoomScale]);
}
}
if (CGImageGetWidth(temporaryImage.CGImage) < CGImageGetHeight(temporaryImage.CGImage)) //If the image is taller than wide, do the following...
{
CGFloat portraitHeight;
if (CGImageGetHeight(temporaryImage.CGImage) * [myScrollView zoomScale] < 368)
{ portraitHeight = 368;}
else {portraitHeight = CGImageGetHeight(temporaryImage.CGImage) * [myScrollView zoomScale];}
if (screenSize.width >= CGImageGetWidth(temporaryImage.CGImage) * [myScrollView zoomScale]) //If the width of the screen is greater than the zoomed width of the image do the following...
{
imageView.frame = CGRectMake(0, 0, 320, portraitHeight);
}
if (screenSize.width < CGImageGetWidth (temporaryImage.CGImage) * [myScrollView zoomScale]) //If the width of the screen is less than the zoomed width of the image do the following...
{
imageView.frame = CGRectMake(0, 0, CGImageGetWidth(temporaryImage.CGImage) * [myScrollView zoomScale], portraitHeight);
}
}
[myScrollView setZoomScale:myScrollView.zoomScale -0.01];
}
Just disable the pagination, so it'll work fine:
scrollview.pagingEnabled = NO;
I had the exact same problem. Here is how I solved
This code should get called as the result of scrollView:DidScroll:
CGFloat imageHeight = self.imageView.frame.size.width * self.imageView.image.size.height / self.imageView.image.size.width;
BOOL imageSmallerThanContent = (imageHeight < self.scrollview.frame.size.height) ? YES : NO;
CGFloat topOffset = (self.imageView.frame.size.height - imageHeight) / 2;
// If image is not large enough setup content offset in a way that image is centered and not vertically scrollable
if (imageSmallerThanContent) {
topOffset = topOffset - ((self.scrollview.frame.size.height - imageHeight)/2);
}
self.scrollview.contentInset = UIEdgeInsetsMake(topOffset * -1, 0, topOffset * -1, 0);
Although the question is a bit old yet the problem still exists. I solved it in Xcode 7 by making the vertical space constraint of the uppermost item (in this case the topLabel) to the superViews (the scrollView) top an IBOutlet and then recalculating its constant every time the content changes depending on the height of the scrollView's subviews (topLabel and bottomLabel).
class MyViewController: UIViewController {
#IBOutlet weak var scrollView: UIScrollView!
#IBOutlet weak var topLabel: UILabel!
#IBOutlet weak var bottomLabel: UILabel!
#IBOutlet weak var toTopConstraint: NSLayoutConstraint!
override func viewDidLayoutSubviews() {
let heightOfScrollViewContents = (topLabel.frame.origin.y + topLabel.frame.size.height - bottomLabel.frame.origin.y)
// In my case abs() delivers the perfect result, but you could also check if the heightOfScrollViewContents is greater than 0.
toTopConstraint.constant = abs((scrollView.frame.height - heightOfScrollViewContents) / 2)
}
func refreshContents() {
// Set the label's text …
self.view.layoutIfNeeded()
}
}
A Swift version to just subclass UIScrollView and lauout the subview by yourself. It works pretty smooth.
import UIKit
class CenteringScrollView: UIScrollView {
override func layoutSubviews() {
super.layoutSubviews()
if zoomScale < 1.0 {
if let subview = self.subviews.first {
subview.center.x = self.center.x
}
}
}
}

Resources