【问题标题】:UIScrollView with centered UIImageView, like Photos app具有居中 UIImageView 的 UIScrollView,如照片应用
【发布时间】:2010-10-12 21:42:13
【问题描述】:

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

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

- (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 的框架。到目前为止,没有什么对我有用。

【问题讨论】:

  • 这是解决此问题的最佳方法:blog.proculo.de/archives/… 它对我来说效果很好。
  • 这是一个与这里描述的完全不同的问题。
  • Martin,看起来 Shizam 为 3.2+ 操作系统提供了一个可行的解决方案。也许您想选择他的答案作为最佳答案。

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


【解决方案1】:

此代码应该适用于大多数 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;
}

【讨论】:

  • 这是到目前为止最好的答案。干净、简单,这正是 Apple 在 WWDC 2010 104 会议上所建议的。请投票!
  • 警告:这段代码并不完美——苹果在 iPad 上渲染的子视图布局比它们的 insets 处理稍慢,所以这个解决方案明显不如 Shizam 的答案流畅。但是,Shizam 的答案有一个小错误,如果您放大边缘,它会突然卡住。用 ios5 在 ipad2 上测试
  • Shizam 的回答对我完全不起作用。这做到了。尽管有时即使这对缩放也没有反应。捏捏似乎总是有效的。
  • 我做了这段代码和一些变体。它的问题在于它发生在缩放动画之后,因此当居中滞后于缩放时会导致“抖动”效果。
  • 这绝对是最好的答案,解决了整个问题。我刚刚在大约 10 分钟内将它翻译成 iOS 版 Xamarin,效果非常好。谢谢!
【解决方案2】:

这就是我所考虑的,其中的解决方案与苹果的照片应用程序完全一样。我一直在使用以下解决方案:

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

【讨论】:

  • 谢谢你!这确实有效(在 3.2+ 上,因为 scrollViewDidZoom 仅适用于 3.2)。对于那些将要尝试它的人:contentSize 显然应该包含 contentInset,因此只需始终将 contentSize 设置为 scrollView.bounds.size。我还发现在 scrollViewDidZoom 中设置 contentOffset 是不必要的,删除它实际上有助于避免一些小跳跃。缩放后 imageView.frame 也未定义(尽管现在恰好可以工作),应替换为 imageView.bounds.size * zoomScale。这是我的看法:gist.github.com/384389
  • 是的,是的,是的!杰出的。这里真正的关键是,在 3.2 中,UIScrollView 似乎终于尊重了非 1.0 缩放比例的 contentInset。
  • 这是对这个问题的唯一真正的复制和粘贴完全有效的答案。干得好,Shizam!
  • 缩小效果非常好,而且非常流畅,但是在放大和缩小之间的切换在此代码中有一个错误,如果你快速捏它就会卡住。似乎这两段代码对中心的解释方式略有不同,当您慢慢放大“zoom=1:1”边界时,动画会隐藏它。大变焦使它非常明显。我还没有解决这个问题,
  • 任何遇到过亚当提到的烦人的“跳跃错误”的人,试试 Andrey Tarantsov Gist。
【解决方案3】:

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

【讨论】:

  • 谢谢...我花了一秒钟才找到它,但这正是我所需要的。它位于 PhotoScroller 示例中。查看 ImageScrollView 类。
【解决方案4】:

我怀疑您需要设置UIScrollViewcontentOffset

【讨论】:

    【解决方案5】:

    我希望事情就这么简单。我在网上做了一些研究,发现这不仅仅是我的问题,而且很多人都在为同样的问题而苦苦挣扎,不仅在 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 的解决方案吗?

    【讨论】:

      【解决方案6】:

      这对我有用:

      - (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 设置为此处捕获的内容。

      【讨论】:

      • 通过什么方法将 contentOffSet 设置为 myScrollViewOffSet ?
      【解决方案7】:

      注意:这种方法很有效。如果图像小于 imageView,它将部分滚动到屏幕外。没什么大不了的,但也没有照片应用那么好。

      首先,重要的是要了解我们正在处理 2 个视图,其中包含图像的图像视图和包含图像视图的滚动视图。 所以,首先将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];
       }
      

      【讨论】:

        【解决方案8】:

        好的,我想我已经找到了一个很好的解决这个问题的方法。诀窍是不断地重新调整 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];
        }
        

        【讨论】:

          【解决方案9】:

          UISCrollView 的内容居中的一种优雅方式是这样。

          UIScrollViewcontentSize添加一个观察者,这样每次内容变化时都会调用这个方法...

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

          【讨论】:

            【解决方案10】:

            在对我有用的 Monotouch 中。

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

            【讨论】:

            • 为了提高您的回答质量,请说明您的帖子将如何/为何解决问题。
            【解决方案11】:

            这是另一种解决方案,类似于@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
                }
            }
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2010-11-29
              • 2012-10-05
              • 2010-10-22
              • 2012-03-02
              • 1970-01-01
              • 2017-01-06
              相关资源
              最近更新 更多