较小时,UIScrollView的中心内容


140

我有一个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)];
}

您曾经彻底解决过这个问题吗?我在同一个问题上挣扎。
约拿(Jonah)2009年

1
注意:如果init不为1(不缩放),则使用initialZoom值来计算该插图。例如,使用以下几行:float topInset =(maxSize.height-imageSize.height * initialZoom)/ 2.0; float sideInset =(maxSize.width-imageSize.width * initialZoom)/ 2.0; 最后设置初始缩放值[imageScrollView setZoomScale:initialZoom];
费利克斯

Answers:


228

我有一个非常简单的解决方案!您需要做的就是在放大ScrollViewDelegate的同时更新子视图(imageview)的中心。如果缩放的图像小于滚动视图,则调整subview.center,否则center为(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);
}

5
对我而言,此解决方案比使用Liam的NYOBetterZoom更加麻烦。也许取决于图像大小等。使用最适合您需求的解决方案
wuf810 2011年

2
Stackoverflow金星奖。我为此苦苦挣扎,但解决方案是如此简单。
n13

2
简化一下:CGFloat offsetX = MAX((scrollView.bounds.size.width - scrollView.contentSize.width) * 0.5, 0.0);
Marek R

6
当滚动视图出现时,这种调整对我有帮助:CGFloat offsetX = MAX((scrollView.bounds.size.width - scrollView.contentInset.left - scrollView.contentInset.right - scrollView.contentSize.width) * 0.5, 0.0); CGFloat offsetY = MAX((scrollView.bounds.size.height - scrollView.contentInset.top - scrollView.contentInset.bottom - scrollView.contentSize.height) * 0.5, 0.0);
Kieran Harper

3
我注意到,这与许多其他居中技术一样,在使用时似乎会遇到问题zoomToRect:contentInset如果您碰巧需要该功能,则使用该方法会更好。有关更多详细信息,请参见petersteinberger.com/blog/2013/how-to-center-uiscrollview
Matej Bukovinski 2015年

51

@EvelynCordner的答案是在我的应用程序中效果最好的答案。代码也比其他选项少得多。

如果有人需要,这是Swift版本:

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

4
这个很棒!缩小后,该视图甚至可以正确设置动画。
卡特·梅德林

4
我将此代码放在一个func中,并在viewDidLayoutSubviews中对其进行了调用,以确保最初已正确设置。
卡特·梅德林

@CarterMedlin的好话对我的Swift 3初始加载有很大帮助。
AJ Hernandez

这似乎可行,但是我不明白为什么,因为它似乎总是计算相同的inset:滚动视图的边界是固定的,内容大小也是固定的。
Vaddadi Kartick

contentSize改变变焦时滚动视图大小只固定
米哈尔Ziobro

25

好的,过去两天来我一直在与这个问题进行斗争,最终找到了一个相当可靠的解决方案(到目前为止...),我想我应该分享它,并减轻其他人的痛苦。:)如果您发现此解决方案有问题,请大喊!

我基本上已经遍历了其他所有人所拥有的东西:搜索StackOverflow,Apple Developer Forums,查看three20,ScrollingMadness,ScrollTestSuite等的代码。我尝试放大UIImageView框架,使用UIScrollView的偏移量和/或插图从ViewController等中获取结果,但没有任何效果很好(其他所有人也发现了)。

睡在上面之后,我尝试了几种其他角度:

  1. 子类化UIImageView,以便它动态地更改其自身的大小-这根本无法正常工作。
  2. 继承UIScrollView的子类,以便它动态更改其自己的contentOffset-这似乎是我的赢家。

通过这种子类化的UIScrollView方法,我重写了contentOffset mutator,因此当图像的缩放比例小于视口时,它不会设置{0,0}-而是设置偏移量,以使图像将在视口中居中。到目前为止,它似乎总是可行。我已经检查了宽,高,小和大的图像,但没有“有效但捏到最小变焦会破坏它”的问题。

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


2
对于那些感兴趣的人-我已经更新了上面的链接项目,因此它对ViewController做“正确的事情”的依赖有所减少,自定义UIScrollView本身会处理更多的细节。
利亚姆·琼斯

2
利亚姆,你摇滚。我说这是ScrollingMadness的作者。iPad上的BTW在3.2+设置contentInset中的scrollViewDidZoom(新的3.2+委托方法)可以正常工作。
安德烈·塔兰佐夫

非常整齐的代码。我已经为此抓挠了一段时间。已将项目添加到handyiphonecode.com
samvermette

这很棒。谢谢。它并没有补偿我的UIStatusBar被隐藏,因此我将行更改anOffset.y = -(scrollViewSize.height - zoomViewSize.height) / 2.0anOffset.y = (-(scrollViewSize.height - zoomViewSize.height) / 2.0) + 10;
Gordon Fontenot 2010年

我认为,bi Erdemus给出的解决方案要简单得多。
gyozo kudor

23

此代码应可在大多数版本的iOS上运行(并且已经过测试,可在3.1或更高版本上运行)。

它基于photocoller的Apple WWDC代码。

将以下内容添加到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;
}

3
这应该是公认的答案,因为它更简单。谢谢。你救了我的命!!!!!!:D
鸭子

删除所有iOS 3.2+ API调用后,居中逻辑似乎仅适用于装有iOS 3.2+的设备,而不适用于3.1.3(跳动,闪烁,获取随机偏移)。我已经比较了3.1.3和3.2+之间的帧原点和大小的输出,即使它们确实匹配,由于某种原因,子视图的位置仍然不正确。很奇怪。只有利亚姆·琼(Liam Jone)的答案对我有用。
David H

1
@Erdemus和@JosephH解决方案都可以工作,但是UIScrollView子类方法似乎更可取:在缩放过程中首次显示视图时连续显示+连续scrollViewDidZoom调用(而每次滚动仅在事实之后调用一次)
SwiftArchitect

22

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

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

1
这对我有用,但是,如果图像较小,以某种方式开头,则此代码(直接调用时)不会更新滚动视图。我需要的是待办事项首先把UIView那里面的UIScrollview到同一中心(view.center = scrollview.center;),然后在scrollViewDidZoom设定的view.frame.origin x,并y0一次。
morksinaanab '16

对我来说也很好用,但是我在主滚动视图中调用dispatch_async的主队列中做了一个。这使视图以居中图像显示。viewWillAppearscrollViewDidZoom:
帕斯卡

对该代码进行调用,viewDidLayoutSubviews以确保在第一次显示视图时正确设置该代码。
卡特·梅德林

21

目前,我正在子类化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演示中看到它的运行情况


6
您无需继承UIScrollView即可实现此方法。Apple允许您在委托方法中查看滚动和缩放事件。具体来说:-(void)scrollViewDidZoom:(UIScrollView *)scrollView;
Rog 2014年

1
我见过的最佳解决方案(即使在使用约束时也可以使用)。
Frizlab 2015年

12

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

- (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来保存图像)

本质上,这是检查图像视图的宽度/高度是否小于屏幕的宽度/高度,如果是,则创建屏幕宽度/高度的一半的插图(如果您希望图像变大,则可以将其放大超出屏幕范围)。

请注意,由于这是一个UIScrollViewDelegate方法,所以请不要忘记将其添加到视图控制器的声明中,以免出现构建问题。


7

苹果已经向iphone开发人员计划的所有成员发布了2010年WWDC会议视频。讨论的主题之一是他们如何创建照片应用程序!!!他们逐步构建了非常相似的应用程序,并免费提供了所有代码。

它也不使用私有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


1
有问题的示例是MyImagePicker,并且很有趣,它也出现了同样的问题。
科林·巴雷特

1
我应该更清楚了。有问题的示例实际上是“ PhotoScroller”而不是“ MyImagePicker”。没错,“ MyImagePicker”无法正常运行。但是,“ PhotoScroller”可以。试试吧。
约拿(Jonah)2010年

您是否还记得他们讨论Photocroller的WWDC视频的标题?
Grzegorz Adam Hankiewicz 2011年

1
它被称为“使用滚动视图设计应用程序”。
约拿(Jonah)

2

好的,这个解决方案对我有用。我有一个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我调整大小。将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;
}

使用这两种方法,我可以拥有一个与照片应用程序类似的视图。


这似乎可以解决问题,这是一个相当简单的解决方案。某些缩放过渡并不完美,但我相信可以解决。我将尝试更多。
hpique 2010年

更新之前解决方案很明确。现在有点混乱。你能澄清一下吗?
hpique 2010年

2

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

UIScrollView -> UIView -> UIImageView

给与您UIView相同的宽高比UIScrollView,然后将其居中UIImageView


谢谢,哈芬奇。也会尝试这个。您可以发布示例代码还是更改我的示例代码以显示如何构造视图层次结构?
hpique

这类作品。除了因为UIView是UIScrollView的大小之外,如果图像较小(即,横向而不是纵向),则可以将部分图像滚动到屏幕之外。照片应用不允许这样做,并且看起来要好得多。
约拿(Jonah)

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

2

如果contentInset不需要其他任何内容,则可以将其用于居中滚动视图的内容。

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中。


1

您可以观察contentSizeUIScrollView 的属性(使用键值观察或类似方法),并contentInsetcontentSize更改小于滚动视图的大小时自动调整。


会尝试。是否可以使用UIScrollViewDelegate方法代替观察contentSize?
hpique

最有可能的; 您可以使用- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale(在developer.apple.com/iphone/library/documentation/UIKit/…中进行了描述),但是我更喜欢观察,contentSize因为否则您最终将放弃新的缩放比例并从视图中找到它。(除非您可以根据视图的相对大小进行一些很棒的数学计算。)
Tim

谢谢,蒂姆。scrollViewDidEndZooming是我正在使用的。查看有问题的代码(在您的第一个回复之后,我已经添加了它)。它几乎可以工作。唯一的问题是,如果在达到minimumZoomScale之后捏住图像,图像将返回左上角。
hpique

您是说它可以从最小缩放比例以外的任何比例进行缩放(使图像居中),并且仅当在最小比例下再次尝试对其进行缩放时才会中断?还是根本不适合缩小到最小规模?

第一个。它使图像居中以便从最小缩放比例以外的任何其他比例进行缩放,并且如果您在达到最小缩放比例后再次尝试对其进行缩放,则会中断该图像。另外,当第一次达到最小缩放比例时,内容会像从顶部一样短暂地动画化,这会产生短暂的怪异效果。至少在3.0仿真器上会发生这种情况。
hpique

1

一种使内容居中的优雅方法UISCrollView是。

在您的contentSize中添加一个观察者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 ); }
  • 您应该更改[pointer viewWithTag:100]。用内容视图替换UIView

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

每当他的尺寸改变时,这将校正内容的中心。

注意:此内容无法正常使用的唯一方法是使用的标准缩放功能UIScrollView


无法使其正常工作。看来您正在居中scrollView而不是其内容。为什么窗口大小很重要?代码是否也应该更正x位置?
hpique 2010年

1

这是我对这个问题的解决方案,它对于滚动视图内的任何视图都可以正常工作。

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

1

这里有很多解决方案,但是我冒着自己的风险。这样做有两个方面的好处:不会像正在更新图像视图框架那样使缩放体验混乱,并且它尊重原始滚动视图插图(例如,在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;

然后在viewDidLoad中的某处填充它:

originalScrollViewInsets = self.scrollView.contentInset;

要将UIImageView放入UIScrollView中(假设UIImage本身位于loadedImage变量中):

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:从UIScrollViewDelegate获得该滚动视图:

- (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本身的大小做出适当的反应),但是对此的修复应该相对容易。


1

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

[self scrollViewDidZoom: scrollView];

在许多情况下,您可能会两次调用此方法,但这是比本主题中的其他一些答案更干净的解决方案。


1

Apple的Photo Scroller Example完全满足您的需求。将其放在您的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; 
}

苹果的照片滚动器示例代码


1

为了使动画流畅,设置

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

这会产生弹跳效果,但不会涉及任何突然的运动。


1

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

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

1

如果你内心的ImageView具有初始特定宽度(例如300),你只是要居中其宽度只有在放大比其初始宽度较小这可以帮助你也。

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

0

这是我目前进行这项工作的方式。更好,但仍然不完美。尝试设置:

 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
}

乔纳 似乎我们已经处理相同的问题已有一段时间了。将检查您的两个解决方案并尽快回复。
hpique 2010年

嗨,乔纳,我尝试了您最新的解决方案。但是,什么是临时图像?我试图把临时图像= imageView.image; 但是,一旦缩放,图像就会消失。谢谢,潘纳格
psanketi 2011年

Pannag,temporaryImage的命名不正确。它应该称为myImage,因为它就是您正在使用的任何图片。对困惑感到抱歉。
约拿(Jonah)

0

好的,我想我已经找到了解决这个问题的不错的方法。诀窍是不断调整imageView's框架。我发现此方法比不断调整contentInsetsor 更好contentOffSets。我不得不添加一些额外的代码来容纳纵向和横向图像。

这是代码:

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

0

只需禁用分页,即可正常使用:

scrollview.pagingEnabled = NO;

0

我有同样的问题。这是我解决的方法

此代码应作为的结果调用 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);

0

尽管问题有点老,但问题仍然存在。我在Xcode 7中解决了此问题,方法是使最上面的项(在本例中为topLabel)到superViews 的垂直空间约束(到scrollView)top an 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()
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.