UIScrollView带有居中的UIImageView,就像Photos app一样

时间:2009-03-12 11:56:06

标签: ios objective-c iphone cocoa-touch uiscrollview

我想要一个带有图像内容视图的滚动视图。图像实际上是比屏幕大得多的地图。地图应该最初位于滚动视图的中心,就像将iPhone转为横向时照片应用中的照片一样。

alt text

我没有设法让中心的地图同时正确缩放和滚动。 如果地图图像从屏幕顶部开始(纵向),则代码如下所示:

- (void)loadView {
    mapView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"map.jpg"]];
    CGFloat mapHeight = MAP_HEIGHT * SCREEN_WIDTH / MAP_WIDTH;
    mapView.frame = CGRectMake(0, 0, SCREEN_WIDTH, mapHeight);
    scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT)];
    scrollView.delegate = self;
    scrollView.contentSize = mapView.frame.size;
    scrollView.maximumZoomScale = MAP_WIDTH / SCREEN_WIDTH;
    scrollView.minimumZoomScale = 1;
    [scrollView addSubview:mapView];
    self.view = scrollView;
}

当我将图像帧移动到中心时,图像仅从其帧的顶部向下生长。我尝试使用mapView变换,动态更改imageView的帧。到目前为止,对我来说没有任何作用。

11 个答案:

答案 0 :(得分:40)

此代码适用于大多数iOS版本(已经过测试,可以在3.1版本上运行)。

它基于Jonah的回答中提到的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;
}

答案 1 :(得分:23)

以下是我要考虑的内容,其中的解决方案与Apple的照片应用程序完全相同。我一直在使用以下解决方案:

-(void) scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale

重新定位,但我不喜欢这个解决方案,因为在缩放完成后,它会反弹然后快速'跳'到中心,这非常不性感。如果你几乎完全相同的逻辑,但在这个委托函数中,结果证明:

-(void)scrollViewDidZoom:(UIScrollView *)pScrollView

它都从中心开始,当你缩小它时保持居中:

-(void)scrollViewDidZoom:(UIScrollView *)pScrollView {
CGRect innerFrame = imageView.frame;
CGRect scrollerBounds = pScrollView.bounds;

if ( ( innerFrame.size.width < scrollerBounds.size.width ) || ( innerFrame.size.height < scrollerBounds.size.height ) )
{
    CGFloat tempx = imageView.center.x - ( scrollerBounds.size.width / 2 );
    CGFloat tempy = imageView.center.y - ( scrollerBounds.size.height / 2 );
    CGPoint myScrollViewOffset = CGPointMake( tempx, tempy);

    pScrollView.contentOffset = myScrollViewOffset;

}

UIEdgeInsets anEdgeInset = { 0, 0, 0, 0};
if ( scrollerBounds.size.width > innerFrame.size.width )
{
    anEdgeInset.left = (scrollerBounds.size.width - innerFrame.size.width) / 2;
    anEdgeInset.right = -anEdgeInset.left;  // I don't know why this needs to be negative, but that's what works
}
if ( scrollerBounds.size.height > innerFrame.size.height )
{
    anEdgeInset.top = (scrollerBounds.size.height - innerFrame.size.height) / 2;
    anEdgeInset.bottom = -anEdgeInset.top;  // I don't know why this needs to be negative, but that's what works
}
pScrollView.contentInset = anEdgeInset;
}

'imageView'是您正在使用的UIImageView

答案 2 :(得分:7)

Apple已向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

答案 3 :(得分:2)

我怀疑您需要设置UIScrollView的{​​{1}}。

答案 4 :(得分:1)

我希望就这么简单。我在网上做了一些研究,发现这不仅仅是我的问题,但很多人不仅在iPhone上遇到同样的问题,而且在Apple的台式机Cocoa上也是如此。 请参阅以下链接:

http://www.iphonedevsdk.com/forum/iphone-sdk-development/5740-uiimageview-uiscrollview.html
所描述的解决方案基于图像的属性UIViewContentModeScaleAspectFit,但不幸的是它不能很好地工作。图像居中并且正常生长,但弹跳区域似乎比图片大得多。

这家伙也没有得到答案:
http://discussions.apple.com/thread.jspa?messageID=8322675

最后,Apple的桌面Cocoa也存在同样的问题:
http://www.cocoadev.com/index.pl?CenteringInsideNSScrollView
我认为解决方案有效,但它基于NSClipView,它不在iPhone上......

有人在iPhone上有解决方案吗?

答案 5 :(得分:1)

这对我有用:

- (void) scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale {
CGFloat tempx = view.center.x-160;
CGFloat tempy = view.center.y-160;
myScrollViewOffset = CGPointMake(tempx,tempy);

}

其中160是UIScrollView的宽度/高度的一半。

然后我将contentoffset设置为此处捕获的内容。

答案 6 :(得分:0)

注意:此方法有点工作。如果图像小于imageView,它将部分滚动屏幕。没什么大不了的,但也不如照片应用程序那么好。

首先,重要的是要了解我们正在处理2个视图,其中包含图像的imageview,以及包含imageview的scrollview。 因此,首先将imageview设置为屏幕大小:

 [myImageView setFrame:self.view.frame];

然后,将图像置于图像视图中心:

 myImageView.contentMode = UIViewContentModeCenter;

这是我的整个代码:

- (void)viewDidLoad {
AppDelegate *appDelegate = (pAppDelegate *)[[UIApplication sharedApplication] delegate];
 [super viewDidLoad];
 NSString *Path = [[NSBundle mainBundle] bundlePath];
 NSString *ImagePath = [Path stringByAppendingPathComponent:(@"data: %@", appDelegate.MainImageName)];
 UIImage *tempImg = [[UIImage alloc] initWithContentsOfFile:ImagePath];
 [imgView setImage:tempImg];

 myScrollView = [[UIScrollView alloc] initWithFrame:[[self view] bounds]];
 [myScrollView addSubview:myImageView];

//Set ScrollView Appearance
 [myScrollView setBackgroundColor:[UIColor blackColor]];
 myScrollView.indicatorStyle = UIScrollViewIndicatorStyleWhite;

//Set Scrolling Prefs
 myScrollView.bounces = YES;
 myScrollView.delegate = self;
 myScrollView.clipsToBounds = YES; // default is NO, we want to restrict drawing within our scrollview
 [myScrollView setCanCancelContentTouches:NO];
 [myScrollView setScrollEnabled:YES];


//Set Zooming Prefs
 myScrollView.maximumZoomScale = 3.0;
 myScrollView.minimumZoomScale = CGImageGetWidth(tempImg.CGImage)/320;
 myScrollView.zoomScale = 1.01; //Added the .01 to enable scrolling immediately upon view load.
 myScrollView.bouncesZoom = YES;


 [myImageView setFrame:self.view.frame];//rect];// .frame.size.height = imageHeight;
 myImageView.contentMode = UIViewContentModeCenter;
 self.view = myScrollView;
 [tempImg release];
 }

答案 7 :(得分:0)

好的,我想我找到了一个很好的解决方案。诀窍是不断重新调整imageView的框架。我发现这比不断调整contentInsets或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];
}

答案 8 :(得分:0)

UISCrollView的内容集中在一起的一种优雅方式就是这个。

将一位观察者添加到UIScrollView contentSize 中,因此每次内容更改时都会调用此方法...

[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的标准缩放功能。

答案 9 :(得分:0)

Monotouch对我有用。

this._scroll.ScrollRectToVisible(new RectangleF(_scroll.ContentSize.Width/2, _scroll.ContentSize.Height/2,1,1),false);

答案 10 :(得分:0)

这是一个替代解决方案,类似于@ JosephH的答案,但是这个解决方案考虑了图像的实际尺寸。因此,当用户平移/缩放时,屏幕上的空格永远不会超过所需的空白。例如,在纵向屏幕上显示风景图像时,这是一个常见问题。当整个图像在屏幕上时(Aspect Fit),图像上方和下方将有空白区域。然后,当放大时,其他解决方案将空白视为图像的一部分,因为它位于imageView中。它们可以让你在屏幕上平移大部分图像,只留下可见的空白。这看起来对用户不利。

使用这个类,你需要传递它正在使用的imageView。我很想让它自动检测,但这更快,你想要layoutSubviews方法可以获得的所有速度。

注意:按原样,这需要为scrollView启用AutoLayout。

//
//  CentringScrollView.swift
//  Cerebral Gardens
//
//  Created by Dave Wood
//  Copyright © 2016 Cerebral Gardens Inc. All rights reserved.
//

import UIKit

class CentringScrollView: UIScrollView {

    var imageView: UIImageView?

    override func layoutSubviews() {
        super.layoutSubviews()

        guard let superview = superview else { return }
        guard let imageView = imageView else { return }
        guard let image = imageView.image else { return }

        var frameToCentre = imageView.frame

        let imageWidth = image.size.width
        let imageHeight = image.size.height

        let widthRatio = superview.bounds.size.width / imageWidth
        let heightRatio = superview.bounds.size.height / imageHeight

        let minRatio = min(widthRatio, heightRatio, 1.0)

        let effectiveImageWidth = minRatio * imageWidth * zoomScale
        let effectiveImageHeight = minRatio * imageHeight * zoomScale

        contentSize = CGSize(width: max(effectiveImageWidth, bounds.size.width), height: max(effectiveImageHeight, bounds.size.height))

        frameToCentre.origin.x = (contentSize.width - frameToCentre.size.width) / 2
        frameToCentre.origin.y = (contentSize.height - frameToCentre.size.height) / 2

        imageView.frame = frameToCentre
    }
}