Skip to content

Instantly share code, notes, and snippets.

@fl034
Forked from algal/ScaleAspectFitImageView.swift
Last active September 24, 2019 09:04
Show Gist options
  • Save fl034/dd4ff288309168a88ca933dcdc9c5a5a to your computer and use it in GitHub Desktop.
Save fl034/dd4ff288309168a88ca933dcdc9c5a5a to your computer and use it in GitHub Desktop.
UIImageView subclass that works with Auto Layout to express its desired aspect ratio
import UIKit
// known-good: Xcode 8.2.1
/**
UIImageView subclass which works with Auto Layout to try
to maintain the same aspect ratio as the image it displays.
This is unlike the usual behavior of UIImageView, where the
scaleAspectFit content mode only affects what the view displays
and not the size it prefers, and so it does not play
well with AL constraints. In particular, UIImageView.intrinsicContentSize
always returns each of the intrinsic size dimensions of the image
itself, not a size that adjusts to reflect constraints on the
view. So if you constrain the width of a UIImageView, for example,
the view's intrinsic content size still declares a preferred
height based on the image's intrinsic height, rather than the
displayed height produced by the scaleAspectFit content mode.
In contrast, this subclass has a few notable properties:
- If you externally constraint one dimension, its internal constraints
will then adjust the other dimension so it holds the image's aspect
ratio.
- Uses a low layout priority to do this. So if you externally
require it to have an incorrect aspect ratio, you do not get conflicts.
- Still uses the scaleAspectFit content mode internally, so if a
client requires an incorrect aspect, you still get scaleAspectFit
behavior to determining what is displayed within whatever
dimensionsare finally used.
- It is a subclass of UIImageView and supports all of UIImageView's
initializers, so it is a drop-in substitute.
*/
@IBDesignable
public class RatioBasedImageView : UIImageView {
/// constraint to maintain same aspect ratio as the image
private var aspectRatioConstraint:NSLayoutConstraint? = nil
public override func prepareForInterfaceBuilder() {
invalidateIntrinsicContentSize()
}
@IBInspectable
var maxAspectRatio: CGFloat = 999 {
didSet {
updateAspectRatioConstraint()
}
}
@IBInspectable
var minAspectRatio: CGFloat = 0 {
didSet {
updateAspectRatioConstraint()
}
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder:aDecoder)
self.setup()
}
public override init(frame:CGRect) {
super.init(frame:frame)
self.setup()
}
public override init(image: UIImage!) {
super.init(image:image)
self.setup()
}
public override init(image: UIImage!, highlightedImage: UIImage?) {
super.init(image:image,highlightedImage:highlightedImage)
self.setup()
}
override public var image: UIImage? {
didSet { self.updateAspectRatioConstraint() }
}
private func setup() {
// self.contentMode = .scaleAspectFit
self.updateAspectRatioConstraint()
}
/// Removes any pre-existing aspect ratio constraint, and adds a new one based on the current image
private func updateAspectRatioConstraint() {
// remove any existing aspect ratio constraint
if let constraint = self.aspectRatioConstraint {
self.removeConstraint(constraint)
}
self.aspectRatioConstraint = nil
if let imageSize = image?.size, imageSize.height != 0 {
var aspectRatio = imageSize.width / imageSize.height
aspectRatio = max(minAspectRatio, aspectRatio)
aspectRatio = min(maxAspectRatio, aspectRatio)
let constraint = NSLayoutConstraint(item: self, attribute: .width,
relatedBy: .equal,
toItem: self, attribute: .height,
multiplier: aspectRatio, constant: 0)
// priority above fitting size level and below low
constraint.priority = .required
// constraint.priority = .init(rawValue: (UILayoutPriority.defaultLow.rawValue + UILayoutPriority.fittingSizeLevel.rawValue) / 2.0)
self.addConstraint(constraint)
self.aspectRatioConstraint = constraint
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment