I want my UIImageView
to grow or shrink depending on the size of what the actual image it's displaying is. But I want it to stay vertically centered and 10pts from the leading edge of the superview.
However, if I set these two constraints it complains I haven't set up enough constraints to satisfy Auto Layout. To me it seems to perfectly describe what I want. What am I missing?
A vanilla UIView instance, for example, does not have an intrinsic content size. This means that you need to add four explicit constraints to describe the size and position of a UIView instance in a user interface.
If you are resizing UIImageView manually, set the content mode to UIViewContentModeScaleAspectFill . If you want to keep content mode UIViewContentModeScaleAspectFit do not resize imageview to 313. Image will adjust maximum possible width and height , keeping it's aspect ratio.
UIImage contains the data for an image. UIImageView is a custom view meant to display the UIImage .
Open the Library, look for "Tap Gesture Recognizer" object. Drag the object to your storyboard, and set the delegate to the image you want to trigger actions. Then go to the view controller, drag the same object to set the IBAction.
The image view's intrinsic size is already dependent on the size of the image. Your assumptions (and constraints are correct).
However, if you've set up your image view in interface builder and have not provided it with an image, then the layout system (interface builder) won't know how big your image view is supposed to be at compile time. Your layout is ambiguous because your image view could be many sizes. This is what throws the errors.
Once you set your image view's image property, then the image view's intrinsic size is defined by the size of the image. If you're setting the view at runtime, then you can do exactly what Anna mentioned and provide interface builder with a "placeholder" intrinsic size in the property inspector of the image view. This tells interface builder, "use this size for now, I'll give you a real size later". The placeholder constraints are ignored at runtime.
Your other option is to assign the image to the image view in interface builder directly (but I assume your images are dynamic, so this won't work for you).
Here is the essential problem: the only way in which UIImageView
interacts with Auto Layout is via its intrinsicContentSize
property. That property provides the intrinsic size of the image itself, and nothing more. This creates three situations, with different solutions.
In the first case, if you place an UIImageView
in a context where external Auto Layout constraints affect only its position, then the view's intrinsicContentSize
will declare it wants to be the size of its image, and Auto Layout will automatically resize the view to equal the size of its image. This is sometimes exactly what you want, and then life is good!
At other times, you're in a different case. You know exactly the size you want the image view to take, but you also want it to preserve the aspect ratio of the displayed image. In this case, you can use Auto Layout to constrain the size of the image, while setting the old contentMode
property to .scaleAspectFit
on the image view. That property will cause the image view to rescale the displayed image, adding padding as needed. And if this is what you want, life is good!
But often times, you're in the tricky third case. You want to use Auto Layout to partially determine the size of the view, while allowing the image's aspect ratio to determine the other part. For instance, you might want to use Auto Layout constraints to determine one dimension of the size (like the width, in a vertical timeline scroll), while relying on the view to tell Auto Layout that it wants a height matching the image's aspect ratio (so the scrollable items are not too short or too tall).
You cannot configure an ordinary image view to do this because because an image view only communicates its intrinsicContentSize
. So if you put an image view in a context where Auto Layout constrains one dimension (for instance, constraining it to a short width), then the image view does not tell Auto Layout it wants its height rescaled in tandem. It continues to report its intrinsic content size, with the unmodified height of the image itself.
In order to configure the image view to tell Auto Layout that it wants to take on the aspect ratio of the image it contains, you need to add a new constraint to that effect. Furthermore, you will need to update that constraint whenever the image itself is updated. You can build a UIImageView
subclass that does this, so the behavior is automatic. Here's an example:
public class ScaleAspectFitImageView : UIImageView {
/// constraint to maintain same aspect ratio as the image
private var aspectRatioConstraint:NSLayoutConstraint? = nil
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 c = self.aspectRatioConstraint {
self.removeConstraint(c)
}
self.aspectRatioConstraint = nil
if let imageSize = image?.size, imageSize.height != 0
{
let aspectRatio = imageSize.width / imageSize.height
let c = NSLayoutConstraint(item: self, attribute: .width,
relatedBy: .equal,
toItem: self, attribute: .height,
multiplier: aspectRatio, constant: 0)
// a priority above fitting size level and below low
c.priority = (UILayoutPriorityDefaultLow + UILayoutPriorityFittingSizeLevel) / 2.0
self.addConstraint(c)
self.aspectRatioConstraint = c
}
}
}
More comment is available at this gist
For @algal case 3, all I had to do was
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)
}
}
This scales the height component of the intrinsicContentSize. (-1.0, -1.0) is returned when there is no image because this is the default behavior in the superclass.
Also, I did not need to set UITableViewAutomaticDimension
for the table with the cell containing the image view, and the cell still sizes automatically. The image view's content mode is Aspect Fit
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With