UIScrollView 的中心内容较小时

我有一个 UIImageView内的 UIScrollView,我用来缩放和滚动。如果滚动视图的图像/内容大于滚动视图,则一切正常。但是,当图像变得比滚动视图小时,它会粘附在滚动视图的左上角。我想保持它的中心,像照片应用程序。

UIScrollView变小的时候,有什么关于保持内容居中的想法或例子吗?

我正在使用 iPhone 3.0。

下面的代码几乎可以正常工作。如果我在达到最小缩放水平后捏它,图像就会返回到左上角。

- (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)];
}
79497 次浏览

您可以观察 UIScrollView 的 contentSize属性(使用键值观察或类似方法) ,并在 contentSize变化小于滚动视图大小时自动调整 contentInset

我这样做的方式是在层次结构中添加一个额外的视图:

UIScrollView -> UIView -> UIImageView

给你的 UIView相同的高宽比作为你的 UIScrollView,并中心你的 UIImageView到那。

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.

密码是这样的:

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

UISCrollView的内容集中在一起的一种优雅的方法是这样的。

将一个观察者添加到 UIScrollView大小,因此每次内容发生变化时都会调用这个方法..。

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

现在用观察者的方法:

- (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 ); }
  • 您应该更改 < code > [指针 ViewWithTag: 100] 内容视图。

    • 也改变 imageGallery指向您的窗口大小。

这将纠正内容的中心每次他的大小变化。

注意: 此内容不能很好工作的唯一方法是使用 UIScrollView的标准缩放功能。

下面是我目前使用的方法。它更好,但仍不完美。尝试设置:

 myScrollView.bouncesZoom = YES;

修正了在 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];
}

另外,我做以下工作是为了防止图像在缩小后粘在一边。

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

好吧,这个方法对我很有效。我有一个子类的 UIScrollView与一个参考的 UIImageView它正在显示。每当 UIScrollView缩放时,contentSize 属性都会进行调整。在二传手中,我适当地调整了 UIImageView的比例,并且调整了它的中心位置。

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

每次我设置的图像上的 UIScrollView我调整它的大小。Scrollview 中的 UIScrollView也是我创建的自定义类。

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

通过这两种方法,我可以得到一个像照片应用程序一样的视图。

好吧,过去的两天我一直在和这个问题作斗争,最终找到了一个相当可靠的解决方案(到目前为止... ...) ,我认为我应该分享它,给别人减少一些痛苦。:)如果你发现这个解决方案有问题,请大声喊出来!

我基本上经历了所有人都经历过的事情: 搜索 StackOverflow,苹果开发者论坛,查看320的代码,ScrollingMadness,ScrollTestSuite 等等。我尝试过放大 UIImageView 框架,从 ViewController 中调用 UIScrollView 的偏移量和/或插入值,等等,但都没有什么效果(其他人也都发现了)。

睡了一觉之后,我尝试了几个不同的角度:

  1. 子类化 UIImageView 以便动态改变它自己的大小-这根本不能很好地工作。
  2. 子类化 UIScrollView,这样它就可以动态地改变它自己的 contentOffset ——这个对我来说似乎是一个赢家。

通过这个子类化 UIScrollView 方法,我重写 contentOffset 变量,这样当图像缩放比 viewport 小时,它就不会设置{0,0} ,而是设置偏移量,使图像保持在 viewport 中心。到目前为止,它似乎总是工作。我已经检查了宽,高,小和大图像,没有“工程,但捏在最小变焦打破它”的问题。

我已经向 github 上传了一个使用这个解决方案的示例项目,你可以在这里找到它: http://github.com/nyoron/NYOBetterZoom

我有个很简单的办法! 所有您需要的是更新您的子视图(图像视图)的中心,同时缩放在 ScrollView 委托。 如果缩放的图像小于滚动视图,则调整 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);
}

苹果公司已经发布了2010年全球开发者大会的会议视频,所有成员的 iphone 开发者计划。讨论的主题之一是他们是如何创建照片应用程序的! ! !他们一步一步地构建了一个非常相似的应用程序,并且让所有的代码都可以免费获得。

它也不使用私有 API。我不能把任何代码放在这里,因为保密协议,但这里有一个示例代码下载链接。您可能需要登录才能访问。

Http://connect.apple.com/cgi-bin/webobjects/membersite.woa/wa/getsoftware?code=y&source=x&bundleid=20645

下面是 iTunes WWDC 页面的链接:

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

这段代码应该可以在大多数版本的 iOS 上运行(并且已经在3.1以上版本中测试过)。

它是基于苹果公司的 WWDC 代码为 Photocoler。

将下面的代码添加到 UIScrollView 的子类中,并将 tileContainerView 替换为包含您的图像或瓷砖的视图:

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

我花了一天的时间来解决这个问题,最终实现了 缩放:,如下所示:

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

这样可以确保当图像比屏幕小时,它周围仍然有足够的空间,这样您就可以将它放置到您想要的确切位置。

(假设您的 UIScrollView 包含保存图像的 UIImageView)

基本上,这样做是检查你的图像视图的宽度/高度是否小于屏幕的宽度/高度,如果是的话,创建一个宽度/高度的一半的插入(如果你想让图像超出屏幕边界,你可以把它变大)。

注意,因为这是一个 代理方法,所以不要忘记将它添加到视图控制器的声明中,以避免出现构建问题。

您会发现 Erdemus 发布的解决方案确实有效,但是... ... 在某些情况下,scrollViewDidZoom 方法没有被调用,您的图像被粘在左上角。一个简单的解决方案是在最初显示图像时显式调用该方法,如下所示:

[self scrollViewDidZoom: 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];
}
}

这里有很多解决方案,但我愿意冒险把我自己的解决方案放在这里。它的好处有两个原因: 它不会影响缩放体验,就像在更新图像视图框架一样,而且它尊重原始的滚动视图插入(比如,在 xib 或故事板中定义,用于优雅地处理半透明的工具栏等)。

首先,定义一个小型助手:

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

为了保留原始的插图,定义字段:

UIEdgeInsets originalScrollViewInsets;

在视野中的某个地方,填充它:

originalScrollViewInsets = self.scrollView.contentInset;

要将 UIImageView 放入 UIScrollView (假设 UIImage 本身在 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: 该滚动视图的来自 UIScrollViewGenerate:

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

最后,以自身为中心:

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

我还没有测试方向的改变(也就是说,调整 UIScrollView 本身大小的正确反应) ,但是修复这个问题应该相对容易。

苹果的图片滚动器例子正是你所寻找的。将其放入 UIScrollView 子类中,并将 _ zoomView 更改为 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;
}

苹果的图片滚动器样本代码

只要禁用分页,就可以正常工作:

scrollview.pagingEnabled = NO;

目前,我子类化 UIScrollView和覆盖 setContentOffset:来调整基于 contentSize的偏移量。它既可以用于捏缩,也可以用于程序缩放。

@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

此代码除了短小精悍之外,还产生了比@Erdemus 解决方案更平滑的缩放。您可以在 RMGallery演示中看到它的运行情况。

我也有同样的问题,我是这样解决的

这段代码应该作为 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);

要使动画流畅,请设置

self.scrollview.bouncesZoom = NO;

并使用这个函数(使用 这个答案的方法找到中心)

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

这就产生了弹跳效果,但事先并不涉及任何突然的动作。

要找到更适合使用自动布局的滚动视图的解决方案,请使用滚动视图的内容集,而不是更新滚动视图子视图的框架。

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

虽然这个问题有点老,但问题仍然存在。我在 Xcode 7中解决这个问题的方法是: 将最上面的项目(在这个例子中是 topLabel)的垂直空间约束设置为超级视图(scrollView)顶部的 IBOutlet,然后根据 scrollView 子视图(topLabelbottomLabel)的高度每次内容变化时重新计算其常数。

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

只是快速的批准答案,但不使用委托进行子类化

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

@ EvelynCordner 的回答 是我的应用程序中运行得最好的一个,比其他选项的代码也少得多。

如果有人需要,以下是斯威夫特的版本:

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

如果你的内部图像视图有初始特定宽度(如300) ,你只是想中心的宽度 只有缩放小于它的初始宽度这可能也会帮助你。

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

我知道上面的一些答案是正确的,但我只是想给我的答案一些解释,评论会让你明白为什么我们这样做。

当我第一次加载 scrollView 时,我编写了以下代码使其居中,请注意我们先设置 contentOffset,然后设置 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)

然后,当我捏 vContent 时,我编写以下代码使其居中。

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

如果其他任何事情都不需要 contentInset,它可以用来居中 scrollview 的内容。

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

如果这种方法的优点是您仍然可以使用 contentLayoutGuide将内容放置在 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)
])

或者直接拖放 Xcode Interface Builder 的内容。

一个快速的版本,只是子类 UIScrollView和布局自己的子视图。它的工作相当顺利。

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