如何在Swift 3中根据UIImage的大小/比例调整UIImageView的大小?

85
我有一个UIImageView,用户可以下载各种格式的UIImages。问题是,我需要UIImageView根据给定图像的比例进行调整大小。
目前,我正在使用Aspect fit,但是UIImageView仍然在其大部分空白处保持为空。我希望UIImageView根据其内容自动调整大小。例如,如果图片是1:1、4:3、6:2、16:9等。

enter image description here

我很感激你的帮助。

如您所请求,以下是我想要的内容:

enter image description here

我有一个正方形的 UIImageView,其中加载了一个 16:7 或其他比例的图像,UIImageView 被调整大小以适应图像的大小...

'Aspect fill' 怎么样? - Satachito
3
Aspect fill 远远不是一个选项,因为它会裁剪图片... - David Seek
1
如果您不确定您的理想结果是什么样子,请发布一张您希望您的应用程序实现的图片。 - Yohst
2
@Yohst,已发布。我认为我的请求很清楚了...如果不是这样的话,对不起。 - David Seek
我有同样的问题,我找到了解决方案按比例调整高宽 - kalpesh
15个回答

87

我花了很多时间寻找解决方案,针对你遇到的相同问题,这是唯一有效的解决方案(Swift 4,Xcode 9.2):

class ScaledHeightImageView: UIImageView {

    override var intrinsicContentSize: CGSize {

        if let myImage = self.image {
            let myImageWidth = myImage.size.width
            let myImageHeight = myImage.size.height
            let myViewWidth = self.frame.size.width
 
            let ratio = myViewWidth/myImageWidth
            let scaledHeight = myImageHeight * ratio

            return CGSize(width: myViewWidth, height: scaledHeight)
        }

        return CGSize(width: -1.0, height: -1.0)
    }

}

将该类添加到项目中,并将UIImageView设置为自定义类ScaledHeightImageView。图片视图的内容模式是Aspect Fit。

我的问题与此帖子中所述的相同。在原型TableViewCell的ContentView中,我有一个垂直的StackView,每个边都受到约束。在StackView内部有一个Label、ImageView和另一个Label。将ImageView设置为AspectFit是不够的。图片的大小和比例是正确的,但ImageView没有包裹实际的图片,留下了很多额外的空间在图像和标签之间(就像上面的图片一样)。ImageView的高度似乎与原始图像的高度匹配,而不是调整大小后的图像的高度(在aspectFit完成其工作后)。我发现的其他解决方案由于各种原因并没有完全解决问题。我希望这能帮助某些人。


8
这段代码对我来说非常有效,可以调整从Web上加载的图片的高度,使其填满整个UIImageView的宽度。我只需要将内容模式设置为Aspect Fill(很重要),并且设置视图的高度约束,而是在Size inspector底部将内在大小设置为占位符,并在下面的高度字段中输入任何值。 - javaxian
1
优秀的解决方案 - 比设置内容模式更好地控制大小。我认为很容易忘记有可重写的属性,比如这个。谢谢! - SomaMan
2
这是一个很棒的解决方案。非常感谢,olearyj234 :) - Hamid Hoseini
1
在一些罕见的情况下,这个解决方案对我还没有起作用。特别是在使用StackView中的ImageView时。这个评论以及这个链接的代码片段帮助我完成了它。 - Johnson_145
2
这个怎么用来在图片发布/添加后设置UITableViewCell的大小?这确实可以工作并调整UIImageView的大小,但我只有在滚动表视图后才能看到变化。 - Luke Irvin
显示剩余7条评论

68

我花了很多时间,最后找到了一种对我有效的解决方案(Swift 3):

  • 在IB中,将UIImageView的“Content Mode”设置为“Aspect Fit”
  • 在IB中,将UIImageView的宽度约束设置为您想要的任何值(在我的情况下,是视图的宽度)
  • 在IB中,将UIImageView的高度约束设置为0,并为其创建一个引用出口(例如,constraintHeight

然后,当我需要显示图像时,我只需简单地编写以下内容(从上面的答案中提取的示例):

let ratio = image.size.width / image.size.height
let newHeight = myImageView.frame.width / ratio
constraintHeight.constant = newHeight
view.layoutIfNeeded()

基本上,这确保图像填充UIImageView的宽度,并强制UIImageView的高度等于图像缩放后的高度。


2
我认为最简单的解决方案。 - Cameron Porter
我喜欢这个解决方案!结构良好。 - waseefakhtar
在iPad上没有工作...仍然有宽度上的空白。 - Viktor Vostrikov
感谢提供简单的解决方案 :) - Karan Alangat
这是一个非常简单且非常有效的解决方案! - Lance Samaria
显示剩余10条评论

46

看起来你想根据图片的比例和容器视图的大小调整ImageView的大小,这是Swift的示例(抱歉,之前的答案有一个错误,我已经修复了):

   let containerView = UIView(frame: CGRect(x:0,y:0,width:320,height:500))
   let imageView = UIImageView()

    if let image = UIImage(named: "a_image") {
        let ratio = image.size.width / image.size.height
        if containerView.frame.width > containerView.frame.height {
            let newHeight = containerView.frame.width / ratio
            imageView.frame.size = CGSize(width: containerView.frame.width, height: newHeight)
        }
        else{
            let newWidth = containerView.frame.height * ratio
            imageView.frame.size = CGSize(width: newWidth, height: containerView.frame.height)
        }
    }

@DavidSeek,根据您的截图,宽度似乎是固定的,只有高度是动态的。如果是这样,请使用以下语句获取高度:let newHeight = containerView.frame.width / ratio以获得动态高度。 - Yun CHEN
@DavidSeek,让newHeight = self.view.frame.width / ratio。 - Yun CHEN
我会在几分钟内查看它。 - David Seek
@DavidSeek,如果你正在使用 - Yun CHEN
1
对我来说,使用 containerView.frame.width < containerView.frame.height 而不是 containerView.frame.width > containerView.frame.height 是有效的。此外,如果图像具有相同的高度和宽度,则此方法无法正常工作。 - John Codeos
显示剩余7条评论

12

SWIFT 5

这是我在项目中做的事情:

在ViewController中放置一个ImageView并在viewDidLoad()中创建一个名为imageView的outlet。

override func viewDidLoad() {
        super.viewDidLoad()

        var image = UIImage(contentsOfFile: "yourFilePath")!
        var aspectR: CGFloat = 0.0

        aspectR = image.size.width/image.size.height

        imageView.translatesAutoresizingMaskIntoConstraints = false
        imageView.image = image
        imageView.contentMode = .scaleAspectFit

        NSLayoutConstraint.activate([
        imageView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
        imageView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
        imageView.leadingAnchor.constraint(greaterThanOrEqualTo: view.leadingAnchor),
        imageView.trailingAnchor.constraint(lessThanOrEqualTo: view.trailingAnchor),
        imageView.heightAnchor.constraint(equalTo: imageView.widthAnchor, multiplier: 1/aspectR)
        ])
}
NSLayoutConstraint.activate的最后3行数组确保图片宽度保持在容器视图的范围内,并且高度与宽度成比例(即保持纵横比并将imageView的高度缩小到最小所需值)。
界面生成器中的视图控制器:Main.storyboard 运行应用程序中UIImageView的快照:appSnapshot

2
对于任何寻找自动布局答案的人,我推荐这个答案!非常聪明,谢谢Ankit! - Jan-Dawid Roodt
1
我同意Jan的观点 - 我查看了很多答案,这个在自动布局中起作用!谢谢! - kirikintha

3
我所使用的解决方案基于olearyj234的解决方案, 但它使没有图像的占用空间基本为零(或更具体地说,是最小的iOS可接受值)。它还使用ceil来避免在UIImageView嵌入到诸如滚动单元格之类的东西中时可能出现的非整数值问题。
class FixedWidthAspectFitImageView: UIImageView
{

    override var intrinsicContentSize: CGSize
    {
        // VALIDATE ELSE RETURN
        // frameSizeWidth
        let frameSizeWidth = self.frame.size.width

        // image
        // ⓘ In testing on iOS 12.1.4 heights of 1.0 and 0.5 were respected, but 0.1 and 0.0 led intrinsicContentSize to be ignored.
        guard let image = self.image else
        {
            return CGSize(width: frameSizeWidth, height: 1.0)
        }

        // MAIN
        let returnHeight = ceil(image.size.height * (frameSizeWidth / image.size.width))
        return CGSize(width: frameSizeWidth, height: returnHeight)
    }

}

这个如何与在UITableView单元格中有UIImageView的情况下使用?我的目标是当用户发布新图片(类似Instagram)时,第一次reloadData时单元格会自动调整大小,而不需要将单元格滚动到屏幕外才能更新其高度。 - Luke Irvin
将FixedWidthAspectFitImageView类的UIImageView的四个边约束到单元格的边缘(或固定在单元格边缘的元素),单元格的内在内容大小将根据加载到其中的图像而变为所需大小。如果您的目标是尽快正确调整单元格大小,则需要立即设置图像。如果稍后更改图像,我希望reloadData足以重置单元格高度,但我还没有尝试过。 - John Bushnell
如何“立即设置图像”?我已经尝试过这个方法,但是单元格仍然加载错误,我必须再次调用reloadData才能看到单元格重新调整大小。 - Luke Irvin

1
很多答案在计算intrinsicContentSize时使用frame。docs不鼓励这样做,因为:
此内在大小必须独立于内容框架,因为无法根据高度的变化动态地将更改的宽度通知布局系统。
我发现希望根据以下内容动态设置UIImageView的高度是一个常见的问题:
图像的纵横比
固定宽度
下面提供了一种可能的解决方案。
解决方案
我认为最好的解决方法是向UIImageView添加NSLayoutConstraint,该约束限制widthAnchor和heightAnchor(或反之亦然),以使multiplier与图像的纵横比匹配。我创建了一个UIImageView子类,正是如此。
import UIKit

/// `AdjustableImageView` is a `UIImageView` which should have a fixed width or height.
/// It will add an `NSLayoutConstraint` such that it's width/height (aspect) ratio matches the
/// `image` width/height ratio.
class AdjustableImageView: UIImageView {

    /// `NSLayoutConstraint` constraining `heightAnchor` relative to the `widthAnchor`
    /// with the same `multiplier` as the inverse of the `image` aspect ratio, where aspect
    /// ratio is defined width/height.
    private var aspectRatioConstraint: NSLayoutConstraint?

    /// Override `image` setting constraint if necessary on set
    override var image: UIImage? {
        didSet {
            updateAspectRatioConstraint()
        }
    }

    // MARK: - Init

    override init(image: UIImage?) {
        super.init(image: image)
        setup()
    }

    override init(frame: CGRect) {
        super.init(frame: frame)
        setup()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setup()
    }

    // MARK: - Setup

    /// Shared initializer code
    private func setup() {
        // Set default `contentMode`
        contentMode = .scaleAspectFill

        // Update constraints
        updateAspectRatioConstraint()
    }

    // MARK: - Resize

    /// De-active `aspectRatioConstraint` and re-active if conditions are met
    private func updateAspectRatioConstraint() {
        // De-active old constraint
        aspectRatioConstraint?.isActive = false

        // Check that we have an image
        guard let image = image else { return }

        // `image` dimensions
        let imageWidth = image.size.width
        let imageHeight = image.size.height

        // `image` aspectRatio
        guard imageWidth > 0 else { return }
        let aspectRatio = imageHeight / imageWidth
        guard aspectRatio > 0 else { return }

        // Create a new constraint
        aspectRatioConstraint = heightAnchor.constraint(
            equalTo: widthAnchor,
            multiplier: aspectRatio
        )

        // Activate new constraint
        aspectRatioConstraint?.isActive = true
    }
}

1
如果内容模式设置为 aspectFitaspectFill,答案会有所不同:
extension UIImageView {
var intrinsicScaledContentSize: CGSize? {
    switch contentMode {
    case .scaleAspectFit:
        // aspect fit
        if let image = self.image {
            let imageWidth = image.size.width
            let imageHeight = image.size.height
            let viewWidth = self.frame.size.width
            
            let ratio = viewWidth/imageWidth
            let scaledHeight = imageHeight * ratio
            
            return CGSize(width: viewWidth, height: scaledHeight)
        }
    case .scaleAspectFill:
        // aspect fill
        if let image = self.image {
            let imageWidth = image.size.width
            let imageHeight = image.size.height
            let viewHeight = self.frame.size.width
            
            let ratio = viewHeight/imageHeight
            let scaledWidth = imageWidth * ratio
            
            return CGSize(width: scaledWidth, height: imageHeight)
        }
    
    default: return self.bounds.size
    }
    
    return nil
}

}


1
该解决方案也基于olearyj234的解决方案,但我认为这将帮助更多的人。
@IBDesignable
class DynamicImageView: UIImageView {

    @IBInspectable var fixedWidth: CGFloat = 0 {
        didSet {
            invalidateIntrinsicContentSize()
        }
    }
    @IBInspectable var fixedHeight: CGFloat = 0 {
        didSet {
            invalidateIntrinsicContentSize()
        }
    }

    override var intrinsicContentSize: CGSize {
        var size = CGSize.zero
        if fixedWidth > 0 && fixedHeight > 0 { // 宽高固定
            size.width = fixedWidth
            size.height = fixedHeight
        } else if fixedWidth <= 0 && fixedHeight > 0 { // 固定高度动态宽度
            size.height = fixedHeight
            if let image = self.image {
                let ratio = fixedHeight / image.size.height
                size.width = image.size.width * ratio
            }
        } else if fixedWidth > 0 && fixedHeight <= 0 { // 固定宽度动态高度
            size.width = fixedWidth
            if let image = self.image {
                let ratio = fixedWidth / image.size.width
                size.height = image.size.height * ratio
            }
        } else { // 动态宽高
            size = image?.size ?? .zero
        }
        return size
    }

}

1

iOS 根据 UIImage 大小创建 UIImageView

基于框架的[关于]

func addImageView(image: UIImage, x: CGFloat = 0, y: CGFloat = 0) {
    let imageViewSize = self.getImageViewSize(image: image, minSize: minSize)
    let imageView = UIImageView(
        frame: CGRect(
            x: x,
            y: y,
            width: imageViewSize.width,
            height: imageViewSize.height
        )
    )
    imageView.image = image

    self.view.addSubview(imageView)
}

func getImageViewSize(image: UIImage, minSize: Double) -> CGSize {
    let imageSize = image.size
    
    let imageWidth = imageSize.width
    let imageHeight = imageSize.height
    
    let imageRatio = imageWidth/imageHeight
    
    if imageWidth < imageHeight {
        //portrait
        return CGSize(width: minSize, height: minSize / imageRatio)
    } else {
        //landscape
        return CGSize(width: minSize * imageRatio, height: minSize)
    }
}

使用AutoLayout可以调整约束(例如宽度和高度)。
最大宽度为maxWidth,最大高度为maxHeight。
func getViewSize(size: CGSize, maxSize: CGSize) -> CGSize {
    let width = size.width
    let height = size.height
    
    let maxWidth = maxSize.width
    let maxHeight = maxSize.height
    
    let resultWidth = maxHeight * width / height
    if resultWidth <= maxWidth {
        return CGSize(width: resultWidth, height: maxHeight)
    } else {
//        resultHeight <= maxHeight
        let resultHeight = maxWidth * height / width
        return CGSize(width: maxWidth, height: resultHeight)
    }
}

func setImage(_ image: UIImage?) {
    if let image = image {
        if let superView = self.uiImageView.superview {
            let size = getViewSize(size: image.size, maxSize: superView.frame.size)
            
            self.uiImageWidthConstraint.constant = size.width
            self.uiImageHeightConstraint.constant = size.height
        }
    }
}

0

将您的imageView设置为aspectFit,这将调整图像大小以不超过您的imageView框架。

您可以通过this question中的逻辑获取imageView的UIImage的大小 - 基本上只需获取UIImage的高度和宽度。

计算比率并设置imageView的宽度/高度以适应屏幕。

还有一个similar question与您的问题类似,您可能会从中获得答案。


您的问题表述有误。引用OP的话:“我想让UIImageView根据其内容自行调整大小。” 这意味着:他想将UIImageView的大小调整为包含的UIImage的大小。他并不想根据UIImage的大小来调整包含该UIImageUIImageView的大小。换句话说,他希望让UIImage确定UIImageView的大小。他不想让UIImageView决定UIImage的大小。 - Utku
@Utku 不确定你是如何理解 OP 的问题的,但我基本上给出了被接受答案的理论而没有代码...但如果你需要更简单的解释,我建议获取图像的纵横比,然后将 UIImageView 设置为相同的比例,这样就不会有多余的空间。 - Ben Ong

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接