将可缩放图像保留在UIScrollView的中心

在我的iPhone应用程序中,我需要为用户提供在屏幕上缩放/平移大图像的function。 这很简单:我使用UIScrollView ,设置最大/最小比例因子和缩放/平移按预期工作。 事情变得有趣。 从服务器接收的图像是动态的。 它可以有任何尺寸。 当图像首次加载时,它会缩小(如果需要)以完全适合UIScrollView并在滚动视图中居中 – 屏幕截图如下:

在此处输入图像描述

由于图像的比例与滚动视图的比例不同,因此在图像的上方和下方添加了空白区域,以便图像居中。 但是,当我开始缩放图像时,实际图像变得足够大以填充整个滚动视图视口,因此不再需要顶部/底部的白色填充,但是它们仍保留在那里,如此截图所示:

在此处输入图像描述

我相信这是因为包含图像的UIImageView自动resize以填充整个UIScrollView并且在缩放时,它只是按比例增长。 它将比例模式设置为Aspect FitUIScrollView的委托viewForZoomingInScrollView只返回图像视图。

我试图在scrollViewDidEndZooming方法中重新计算并重新设置UIScrollViewcontentSize和图像视图的大小:

 CGSize imgViewSize = imageView.frame.size; CGSize imageSize = imageView.image.size; CGSize realImgSize; if(imageSize.width / imageSize.height > imgViewSize.width / imgViewSize.height) { realImgSize = CGSizeMake(imgViewSize.width, imgViewSize.width / imageSize.width * imageSize.height); } else { realImgSize = CGSizeMake(imgViewSize.height / imageSize.height * imageSize.width, imgViewSize.height); } scrollView.contentSize = realImgSize; CGRect fr = CGRectMake(0, 0, 0, 0); fr.size = realImgSize; imageView.frame = fr; 

然而,这只会让事情变得更糟(边界仍然存在,但平移不在垂直方向)。

是否有任何方法可以自动减少该空白,因为它变得不必要,然后在放大期间再次增加? 我怀疑这项工作需要在scrollViewDidEndZooming完成,但我不太确定该代码需要什么。

真棒!

谢谢你的代码:)

我只是想加入这个,因为我稍微改了一下以改善行为。

 // make the change during scrollViewDidScroll instead of didEndScrolling... -(void)scrollViewDidZoom:(UIScrollView *)scrollView { CGSize imgViewSize = self.imageView.frame.size; CGSize imageSize = self.imageView.image.size; CGSize realImgSize; if(imageSize.width / imageSize.height > imgViewSize.width / imgViewSize.height) { realImgSize = CGSizeMake(imgViewSize.width, imgViewSize.width / imageSize.width * imageSize.height); } else { realImgSize = CGSizeMake(imgViewSize.height / imageSize.height * imageSize.width, imgViewSize.height); } CGRect fr = CGRectMake(0, 0, 0, 0); fr.size = realImgSize; self.imageView.frame = fr; CGSize scrSize = scrollView.frame.size; float offx = (scrSize.width > realImgSize.width ? (scrSize.width - realImgSize.width) / 2 : 0); float offy = (scrSize.height > realImgSize.height ? (scrSize.height - realImgSize.height) / 2 : 0); // don't animate the change. scrollView.contentInset = UIEdgeInsetsMake(offy, offx, offy, offx); } 

这是我的解决方案,适用于任何标签栏或导航栏组合,或者没有,半透明或不透明。

 - (void)scrollViewDidZoom:(UIScrollView *)scrollView { // The scroll view has zoomed, so you need to re-center the contents CGSize scrollViewSize = [self scrollViewVisibleSize]; // First assume that image center coincides with the contents box center. // This is correct when the image is bigger than scrollView due to zoom CGPoint imageCenter = CGPointMake(self.scrollView.contentSize.width/2.0, self.scrollView.contentSize.height/2.0); CGPoint scrollViewCenter = [self scrollViewCenter]; //if image is smaller than the scrollView visible size - fix the image center accordingly if (self.scrollView.contentSize.width < scrollViewSize.width) { imageCenter.x = scrollViewCenter.x; } if (self.scrollView.contentSize.height < scrollViewSize.height) { imageCenter.y = scrollViewCenter.y; } self.imageView.center = imageCenter; } //return the scroll view center - (CGPoint)scrollViewCenter { CGSize scrollViewSize = [self scrollViewVisibleSize]; return CGPointMake(scrollViewSize.width/2.0, scrollViewSize.height/2.0); } // Return scrollview size without the area overlapping with tab and nav bar. - (CGSize) scrollViewVisibleSize { UIEdgeInsets contentInset = self.scrollView.contentInset; CGSize scrollViewSize = CGRectStandardize(self.scrollView.bounds).size; CGFloat width = scrollViewSize.width - contentInset.left - contentInset.right; CGFloat height = scrollViewSize.height - contentInset.top - contentInset.bottom; return CGSizeMake(width, height); } 

为什么它比我迄今为止所能找到的任何其他东西更好:

  1. 它不会读取或修改图像视图的UIView框架属性,因为缩放的图像视图已应用了变换。 请参阅此处 Apple在应用非标识转换时如何移动或调整视图大小时所说的内容。

  2. 启动iOS 7,其中引入了条形图的半透明度,系统将自动调整滚动视图大小,滚动内容插入和滚动指示器偏移。 因此,您也不应在代码中修改这些内容。

仅供参考:在Xcode界面构建器中有用于切换此行为(默认设置)的复选框。 您可以在视图控制器属性中找到它:

自动滚动视图调整

全视图控制器的源代码在此处发布。

您还可以下载整个Xcode项目以查看滚动视图约束设置,并通过将初始控制器指针移动到以下任何路径,在故事板中播放3个不同的预设:

  1. 查看半透明标签和导航栏。
  2. 使用不透明标签和导航栏查看。
  3. 查看没有酒吧。

每个选项都可以使用相同的VC实现正常工作。

我想我明白了。 解决方案是使用委托的scrollViewDidEndZooming方法,并在该方法中根据图像的大小设置contentInset 。 这是方法的样子:

 - (void)scrollViewDidEndZooming:(UIScrollView *)aScrollView withView:(UIView *)view atScale:(float)scale { CGSize imgViewSize = imageView.frame.size; CGSize imageSize = imageView.image.size; CGSize realImgSize; if(imageSize.width / imageSize.height > imgViewSize.width / imgViewSize.height) { realImgSize = CGSizeMake(imgViewSize.width, imgViewSize.width / imageSize.width * imageSize.height); } else { realImgSize = CGSizeMake(imgViewSize.height / imageSize.height * imageSize.width, imgViewSize.height); } CGRect fr = CGRectMake(0, 0, 0, 0); fr.size = realImgSize; imageView.frame = fr; CGSize scrSize = scrollView.frame.size; float offx = (scrSize.width > realImgSize.width ? (scrSize.width - realImgSize.width) / 2 : 0); float offy = (scrSize.height > realImgSize.height ? (scrSize.height - realImgSize.height) / 2 : 0); [UIView beginAnimations:nil context:nil]; [UIView setAnimationDuration:0.25]; scrollView.contentInset = UIEdgeInsetsMake(offy, offx, offy, offx); [UIView commitAnimations]; } 

请注意,我在设置插图时使用动画,否则在添加插图时图像会在scrollview内跳转。 通过动画,它可以滑动到中心。 我正在使用UIView beginAnimationcommitAnimation而不是动画块,因为我需要让应用程序在iphone 3上运行。

这是Genk’s Answer的快速3版本

  func scrollViewDidZoom(_ scrollView: UIScrollView){ let imgViewSize:CGSize! = self.imageView.frame.size; let imageSize:CGSize! = self.imageView.image?.size; var realImgSize : CGSize; if(imageSize.width / imageSize.height > imgViewSize.width / imgViewSize.height) { realImgSize = CGSize(width: imgViewSize.width,height: imgViewSize.width / imageSize.width * imageSize.height); } else { realImgSize = CGSize(width: imgViewSize.height / imageSize.height * imageSize.width, height: imgViewSize.height); } var fr:CGRect = CGRect.zero fr.size = realImgSize; self.imageView.frame = fr; let scrSize:CGSize = scrollView.frame.size; let offx:CGFloat = (scrSize.width > realImgSize.width ? (scrSize.width - realImgSize.width) / 2 : 0); let offy:CGFloat = (scrSize.height > realImgSize.height ? (scrSize.height - realImgSize.height) / 2 : 0); scrollView.contentInset = UIEdgeInsetsMake(offy, offx, offy, offx); // The scroll view has zoomed, so you need to re-center the contents let scrollViewSize:CGSize = self.scrollViewVisibleSize(); // First assume that image center coincides with the contents box center. // This is correct when the image is bigger than scrollView due to zoom var imageCenter:CGPoint = CGPoint(x: self.scrollView.contentSize.width/2.0, y: self.scrollView.contentSize.height/2.0); let scrollViewCenter:CGPoint = self.scrollViewCenter() //if image is smaller than the scrollView visible size - fix the image center accordingly if (self.scrollView.contentSize.width < scrollViewSize.width) { imageCenter.x = scrollViewCenter.x; } if (self.scrollView.contentSize.height < scrollViewSize.height) { imageCenter.y = scrollViewCenter.y; } self.imageView.center = imageCenter; } //return the scroll view center func scrollViewCenter() -> CGPoint { let scrollViewSize:CGSize = self.scrollViewVisibleSize() return CGPoint(x: scrollViewSize.width/2.0, y: scrollViewSize.height/2.0); } // Return scrollview size without the area overlapping with tab and nav bar. func scrollViewVisibleSize() -> CGSize{ let contentInset:UIEdgeInsets = self.scrollView.contentInset; let scrollViewSize:CGSize = self.scrollView.bounds.standardized.size; let width:CGFloat = scrollViewSize.width - contentInset.left - contentInset.right; let height:CGFloat = scrollViewSize.height - contentInset.top - contentInset.bottom; return CGSize(width:width, height:height); } 

这是在Swift 3.1上测试的扩展。 只需创建一个单独的* .swift文件并粘贴下面的代码:

 import UIKit extension UIScrollView { func applyZoomToImageView() { guard let imageView = delegate?.viewForZooming?(in: self) as? UIImageView else { return } guard let image = imageView.image else { return } guard imageView.frame.size.valid && image.size.valid else { return } let size = image.size ~> imageView.frame.size imageView.frame.size = size self.contentInset = UIEdgeInsets( x: self.frame.size.width ~> size.width, y: self.frame.size.height ~> size.height ) imageView.center = self.contentCenter if self.contentSize.width < self.visibleSize.width { imageView.center.x = self.visibleSize.center.x } if self.contentSize.height < self.visibleSize.height { imageView.center.y = self.visibleSize.center.y } } private var contentCenter: CGPoint { return CGPoint(x: contentSize.width / 2, y: contentSize.height / 2) } private var visibleSize: CGSize { let size: CGSize = bounds.standardized.size return CGSize( width: size.width - contentInset.left - contentInset.right, height: size.height - contentInset.top - contentInset.bottom ) } } fileprivate extension CGFloat { static func ~>(lhs: CGFloat, rhs: CGFloat) -> CGFloat { return lhs > rhs ? (lhs - rhs) / 2 : 0.0 } } fileprivate extension UIEdgeInsets { init(x: CGFloat, y: CGFloat) { self.bottom = y self.left = x self.right = x self.top = y } } fileprivate extension CGSize { var valid: Bool { return width > 0 && height > 0 } var center: CGPoint { return CGPoint(x: width / 2, y: height / 2) } static func ~>(lhs: CGSize, rhs: CGSize) -> CGSize { switch lhs > rhs { case true: return CGSize(width: rhs.width, height: rhs.width / lhs.width * lhs.height) default: return CGSize(width: rhs.height / lhs.height * lhs.width, height: rhs.height) } } static func >(lhs: CGSize, rhs: CGSize) -> Bool { return lhs.width / lhs.height > rhs.width / rhs.height } } 

使用方法:

 extension YOUR_SCROLL_VIEW_DELEGATE: UIScrollViewDelegate { func viewForZooming(in scrollView: UIScrollView) -> UIView? { return YOUR_IMAGE_VIEW } func scrollViewDidZoom(_ scrollView: UIScrollView){ scrollView.applyZoomToImageView() } }