10

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

在此处输入图像描述

由于图像的比例与滚动视图的比例不同,因此在图像的上方和下方添加了空白区域,以使图像居中。但是,当我开始缩放图像时,实际图像变得足够大以填充整个滚动视图视口,因此不再需要顶部/底部的白色填充,但是它们仍然存在,从这个屏幕截图中可以看出:

在此处输入图像描述

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

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

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,但我不太确定该代码需要是什么。

4

6 回答 6

18

惊人的!

感谢您的代码:)

只是想我会添加这个,因为我稍微改变了它以改善行为。

// 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);
}
于 2013-07-04T09:07:51.097 回答
16

这是我的解决方案,可与任何选项卡栏或导航栏组合或两者都通用,无论是否半透明。

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

斯威夫特 5:

public func scrollViewDidZoom(_ scrollView: UIScrollView) {
    centerScrollViewContents()
}

private var scrollViewVisibleSize: CGSize {
    let contentInset = scrollView.contentInset
    let scrollViewSize = scrollView.bounds.standardized.size
    let width = scrollViewSize.width - contentInset.left - contentInset.right
    let height = scrollViewSize.height - contentInset.top - contentInset.bottom
    return CGSize(width:width, height:height)
}

private var scrollViewCenter: CGPoint {
    let scrollViewSize = self.scrollViewVisibleSize()
    return CGPoint(x: scrollViewSize.width / 2.0,
                   y: scrollViewSize.height / 2.0)
}

private func centerScrollViewContents() {
    guard let image = imageView.image else {
        return
    }

    let imgViewSize = imageView.frame.size
    let imageSize = 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 frame = CGRect.zero
    frame.size = realImgSize
    imageView.frame = frame

    let screenSize  = scrollView.frame.size
    let offx = screenSize.width > realImgSize.width ? (screenSize.width - realImgSize.width) / 2 : 0
    let offy = screenSize.height > realImgSize.height ? (screenSize.height - realImgSize.height) / 2 : 0
    scrollView.contentInset = UIEdgeInsets(top: offy,
                                           left: offx,
                                           bottom: offy,
                                           right: offx)

    // The scroll view has zoomed, so you need to re-center the contents
    let scrollViewSize = 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(x: scrollView.contentSize.width / 2.0,
                              y: scrollView.contentSize.height / 2.0)

    let center = scrollViewCenter

    //if image is smaller than the scrollView visible size - fix the image center accordingly
    if scrollView.contentSize.width < scrollViewSize.width {
        imageCenter.x = center.x
    }

    if scrollView.contentSize.height < scrollViewSize.height {
        imageCenter.y = center.y
    }

    imageView.center = imageCenter
}

为什么它比我迄今为止在 SO 上能找到的任何东西都好:

  1. 它不会读取或修改图像视图的 UIView 框架属性,因为缩放的图像视图对其应用了变换。请参阅此处Apple 关于如何在应用非身份转换时移动或调整视图大小的说明。

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

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

自动滚动视图调整

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

您还可以下载整个 Xcode 项目以查看滚动视图约束设置,并通过将初始控制器指针移动到以下任何路径来在情节提要中使用 3 个不同的预设:

  1. 使用半透明选项卡和导航栏查看。
  2. 使用不透明选项卡和导航栏查看。
  3. 查看完全没有条形图。

每个选项都适用于相同的 VC 实现。

于 2016-02-28T09:06:02.587 回答
7

我想我明白了。解决方案是使用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];
}

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

于 2012-12-28T13:09:35.253 回答
4

这是Genk's Answer的 swift 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);
    }
于 2017-01-26T04:48:55.913 回答
4

这是在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()
    }
}
于 2017-04-04T07:35:07.847 回答
2

类似于基于设置的不同答案contentInset,但更短。记住设置scrollView.delegate

func scrollViewDidZoom(_ scrollView: UIScrollView) {
    let offsetX = max((scrollView.bounds.size.width - scrollView.contentSize.width) * 0.5, 0.0)
    let offsetY = max((scrollView.bounds.size.height - scrollView.contentSize.height) * 0.5, 0.0)

    scrollView.contentInset = UIEdgeInsets(top: offsetY, left: offsetX, bottom: offsetY, right: offsetX);
}

如果你想看看几个不同的策略,这里有一个值得一看的地方:github & post

于 2020-02-16T14:45:18.510 回答