Centralize o conteúdo do UIScrollView quando menor

Eu tenho um UIImageView dentro de um UIScrollView que eu uso para zoom e rolagem. Se a imagem / conteúdo da exibição de rolagem for maior que a visualização de rolagem, tudo funcionará bem. No entanto, quando a imagem fica menor que a vista de rolagem, ela fica no canto superior esquerdo da visualização de rolagem. Gostaria de mantê-lo centralizado, como o aplicativo Fotos.

Alguma idéia ou exemplo sobre como manter o conteúdo do UIScrollView centrado quando é menor?

Eu estou trabalhando com o iPhone 3.0.

O código a seguir quase funciona. A imagem retorna ao canto superior esquerdo se eu a apertar depois de atingir o nível mínimo de zoom.

 - (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)]; } 

Eu tenho uma solução muito simples! Tudo o que você precisa é atualizar o centro de sua subvisualização (imageview) enquanto aplica zoom no ScrollViewDelegate. Se a imagem com zoom for menor que a vista de rolagem, ajuste 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); } 

@ A resposta da EvelynCordner foi a que funcionou melhor no meu aplicativo. Muito menos código que as outras opções também.

Aqui está a versão Swift, se alguém precisar dela:

 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 = UIEdgeInsetsMake(offsetY, offsetX, 0, 0) } 

Ok, eu tenho lutado com isso pelos últimos dois dias ligado e desligado e tendo finalmente chegado a uma solução bastante confiável (até agora …) eu pensei que deveria compartilhá-lo e salvar os outros um pouco de dor. 🙂 Se você encontrar algum problema com esta solução, por favor, grite!

Basicamente, passei pelo que todo mundo tem: pesquisando o StackOverflow, os fóruns de desenvolvedores da Apple, olhei o código para o three20, ScrollingMadness, ScrollTestSuite, etc. Tentei ampliar o quadro do UIImageView, jogando com o deslocamento e / ou inserções do UIScrollView do ViewController, etc, mas nada funcionou muito bem (como todo mundo descobriu também).

Depois de dormir, tentei alguns ângulos alternativos:

  1. Subclassificando o UIImageView para que ele altere seu próprio tamanho dinamicamente – isso não funcionou bem.
  2. Subclassificando o UIScrollView para que ele altere seu próprio contentOffset dinamicamente – este é o que parece ser um vencedor para mim.

Com esse método UIScrollView de subclass, estou sobrescrevendo o mutador contentOffset, portanto, não está definindo {0,0} quando a imagem é dimensionada menor que a viewport – em vez disso, é definir o deslocamento de tal forma que a imagem seja mantida centralizada na viewport. Até agora, parece sempre funcionar. Eu verifiquei com imagens largas, altas, minúsculas e grandes e não tem o problema “funciona, mas belisque com zoom mínimo”.

Eu fiz o upload de um projeto de exemplo para o github que usa essa solução, você pode encontrá-lo aqui: http://github.com/nyoron/NYOBetterZoom

Este código deve funcionar na maioria das versões do iOS (e foi testado para funcionar em 3.1 para cima).

É baseado no código WWDC da Apple para o photoscoller.

Adicione o seguinte à sua subclass de UIScrollView e substitua tileContainerView pela visualização que contém sua imagem ou blocos:

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

Atualmente estou subclassificando o UIScrollView e substituindo setContentOffset: para ajustar o deslocamento com base no contentSize . Funciona tanto com pinch quanto com zoom programático.

 @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 

Além de ser curto e doce, este código produz um zoom muito mais suave do que a solução @Erdemus. Você pode vê-lo em ação na demonstração do RMGallery .

Para uma solução mais adequada para exibições de rolagem que usam autolayout, use inserções de conteúdo da visualização de rolagem em vez de atualizar os frameworks das subvisualizações da visualização de rolagem.

 - (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); } 

Passei um dia lutando com esse problema e acabei implementando o scrollViewDidEndZooming: withView: atScale: da seguinte maneira:

 - (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); } 

Isso garante que, quando a imagem for menor que a canvas, ainda haja espaço adequado ao redor para que você possa posicioná-la no local exato desejado.

(supondo que seu UIScrollView contenha um UIImageView para manter a imagem)

Essencialmente, o que isto faz é verificar se a largura / altura da sua imagem é menor do que a largura / altura da canvas e, em caso afirmativo, criar uma inserção de metade da largura / altura da canvas (você provavelmente aumentaria se quisesse que a imagem fosse sair dos limites da canvas).

Observe que, como esse é um método UIScrollViewDelegate , não se esqueça de adicioná-lo à declaração do controlador de visualização, para evitar problemas de compilation.

A Apple lançou os vídeos da session da WWDC de 2010 para todos os membros do programa de desenvolvedores do iPhone. Um dos tópicos discutidos é como eles criaram o aplicativo de fotos !!! Eles criam um aplicativo passo a passo muito semelhante e disponibilizaram todo o código gratuitamente.

Ele não usa API privada também. Eu não posso colocar qualquer código aqui por causa do acordo de não divulgação, mas aqui está um link para o download do código de amostra. Você provavelmente precisará fazer o login para obter access.

http://connect.apple.com/cgi-bin/WebObjects/MemberSite.woa/wa/getSoftware?code=y&source=x&bundleID=20645

E aqui está um link para a página WWDC do iTunes:

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, esta solução está funcionando para mim. Eu tenho uma subclass de UIScrollView com uma referência ao UIImageView que está sendo exibido. Sempre que o UIScrollView aumenta, a propriedade contentSize é ajustada. É no setter que eu dimensiono o UIImageView apropriadamente e também ajustei sua posição central.

 -(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 %fx %f",self.zoomScale,size.width,size.height); } 

Sempre que eu definir a imagem no UIScrollView eu a redimensiono. O UIScrollView no scrollview também é uma class personalizada que eu criei.

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

Com esses dois methods, posso ter uma visão que se comporta exatamente como o aplicativo de fotos.

Você pode observar a propriedade contentSize do UIScrollView (usando a observação de valor-chave ou similar) e ajustar automaticamente o contentInset sempre que o contentSize alterado para menos que o tamanho da visualização de rolagem.

Uma maneira elegante de centrar o conteúdo do UISCrollView é essa.

Adicione um observador ao contentSize do seu UIScrollView , então este método será chamado toda vez que o conteúdo for alterado …

 [myScrollView addObserver:delegate forKeyPath:@"contentSize" options:(NSKeyValueObservingOptionNew) context:NULL]; 

Agora, no seu método de observador:

 - (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 ); } 
  • Você deve alterar a [pointer viewWithTag:100] . Substitua pela sua visualização de conteúdo UIView .

    • imageGallery também o imageGallery apontando para o tamanho da sua janela.

Isso corrigirá o centro do conteúdo toda vez que seu tamanho mudar.

NOTA: A única maneira de este conteúdo não funcionar muito bem é com a funcionalidade de zoom padrão do UIScrollView .

Esta é a minha solução para esse problema, que funciona muito bem para qualquer tipo de visão dentro de uma 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]; } } 

Há uma abundância de soluções aqui, mas eu arriscaria colocar aqui a minha. É bom por dois motivos: ele não atrapalha a experiência de zoom, como atualizar o quadro de visualização de imagens em andamento, e também respeita os inserts de exibição de rolagem original (digamos, definidos no xib ou storyboard para uma manipulação elegante de barras de ferramentas semi-transparentes etc) .

Primeiro, defina um pequeno ajudante:

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

Para reter inserções originais, campo definido:

 UIEdgeInsets originalScrollViewInsets; 

E em algum lugar no viewDidLoad preencha:

 originalScrollViewInsets = self.scrollView.contentInset; 

Para colocar o UIImageView no UIScrollView (supondo que o próprio UIImage esteja em 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: de UIScrollViewDelegate para essa visualização de rolagem:

 - (void)scrollViewDidZoom:(UIScrollView *)scrollView { if (scrollView == self.scrollView) { [self centerImageViewInScrollView]; } } 

Finalmente, centrando-se:

 - (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) ); } 

Eu ainda não testei a mudança de orientação (isto é, reação apropriada para resize o próprio UIScrollView), mas consertar isso deve ser relativamente fácil.

Você verá que a solução postada pelo Erdemus funciona, mas… Existem alguns casos em que o método scrollViewDidZoom não é invocado e sua imagem fica presa no canto superior esquerdo. Uma solução simples é invocar explicitamente o método quando você inicialmente exibe uma imagem, assim:

 [self scrollViewDidZoom: scrollView]; 

Em muitos casos, você pode estar invocando esse método duas vezes, mas essa é uma solução mais limpa do que algumas das outras respostas neste tópico.

O Photo Scroller Example da Apple faz exatamente o que você está procurando. Coloque isso na sua subclass UIScrollView e altere _zoomView para ser seu 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; } 

Código de Amostra do Scroller Foto da Apple

A maneira como fiz isso é adicionar uma visão extra na hierarquia:

 UIScrollView -> UIView -> UIImageView 

Dê ao seu UIView a mesma relação de aspecto do seu UIScrollView e centralize seu UIImageView .

Para fazer a animação fluir bem, defina

 self.scrollview.bouncesZoom = NO; 

e use esta function (encontrar o centro usando o método nesta resposta )

 - (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); }]; } 

Isso cria o efeito de salto, mas não envolve movimentos bruscos de antemão.

Apenas a resposta aprovada em rápida, mas sem subclass usando o delegado

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

Caso o seu imageView interno tenha uma largura inicial específica (por exemplo, 300) e você queira apenas centralizar sua largura apenas em um zoom menor que a largura inicial, isso também poderá ajudá-lo.

  func scrollViewDidZoom(scrollView: UIScrollView){ if imageView.frame.size.width < 300{ imageView.center.x = self.view.frame.width/2 } } 

Aqui está a maneira atual de fazer isso funcionar. É melhor, mas ainda não é perfeito. Tente definir:

  myScrollView.bouncesZoom = YES; 

para corrigir o problema com a visualização não centralizada quando em 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.

Aqui está o código:

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