将可缩放图像保留在UIScrollView的中心

时间:2012-12-28 12:27:20

标签: iphone ios uiscrollview zoom image-zoom

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

enter image description here

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

enter image description here

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

我尝试用UIScrollView方法重新计算并重新设置contentSizescrollViewDidEndZooming和图片视图的大小:

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

6 个答案:

答案 0 :(得分:15)

真棒!

感谢您的代码:)

我只是想加入这个,因为我稍微改了一下以改善行为。

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

答案 1 :(得分:10)

这里的解决方案适用于任何标签栏或导航栏组合,或者两者都没有,半透明或不透明。

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

为什么它比我迄今为止所能找到的任何其他东西更好:

  1. 它没有读取或修改图像视图的UIView框架属性,因为缩放的图像视图已应用了变换。请参阅here Apple在应用非身份转换时如何移动或调整视图大小时所说的内容。

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

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

    automatic scroll view adjustments

    完整视图控制器的源代码已发布here

    此外,您可以下载whole Xcode project以查看滚动视图约束设置,并通过将初始控制器指针移动到以下任何路径,在故事板中播放3个不同的预设:

    1. 使用半透明制表符和导航栏查看。
    2. 使用不透明标签和导航栏查看。
    3. 根本没有酒吧查看。
    4. 每个选项都可以使用相同的VC实现正常工作。

答案 2 :(得分:5)

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

请注意,我在设置插图时使用动画,否则在添加插图时图像会在scrollview内跳转。通过动画,它可以滑动到中心。我正在使用UIView beginAnimationcommitAnimation而不是动画块,因为我需要在iphone 3上运行该应用。

答案 3 :(得分:3)

以下是Genk's Answer

的快速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);
    }

答案 4 :(得分:2)

这是在 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()
    }
}

答案 5 :(得分:1)

类似于基于设置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); }

public class URLReader {
    public static void main(String[] args) throws Exception {


// or if you prefer DOM:
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        DocumentBuilder db = dbf.newDocumentBuilder();
        Document doc = db.parse(new URL("https://stackoverflow.com/").openStream());
        int nodes = doc.getChildNodes().getLength();
        System.out.println(nodes + " nodes found");
    }
}

如果您想了解几种不同的策略,那么这里值得一看:githubpost