ios 使用自动布局,如何根据图像动态调整 UIImageView 的大小?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/26833627/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
With Auto Layout, how do I make a UIImageView's size dynamic depending on the image?
提问by Doug Smith
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.
我希望我UIImageView
的增长或缩小取决于它显示的实际图像的大小。但我希望它保持垂直居中,距离超级视图的前缘 10pts。
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?
但是,如果我设置这两个约束,它会抱怨我没有设置足够的约束来满足自动布局。对我来说,它似乎完美地描述了我想要的东西。我错过了什么?
回答by John Grant
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.
一旦你设置了图像视图的图像属性,图像视图的内在尺寸就由图像的尺寸定义。如果您在运行时设置视图,那么您可以完全按照 Anna 提到的方式执行操作,并在图像视图的属性检查器中为界面构建器提供“占位符”固有大小。这告诉界面构建器,“现在使用这个尺寸,稍后我会给你一个真实的尺寸”。占位符约束在运行时被忽略。
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).
您的另一个选择是直接将图像分配给界面构建器中的图像视图(但我假设您的图像是动态的,因此这对您不起作用)。
回答by algal
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.
这是基本问题:UIImageView
与自动布局交互的唯一方式是通过其intrinsicContentSize
属性。该属性提供图像本身的内在大小,仅此而已。这会产生三种情况,具有不同的解决方案。
case 1: view with image's size
案例1:查看图像的大小
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!
在第一种情况下,如果您将 an 放置UIImageView
在外部自动布局约束仅影响其位置的上下文中,则视图intrinsicContentSize
将声明它希望是其图像的大小,并且自动布局将自动调整视图的大小以等于其大小它的形象。这有时正是你想要的,然后生活就是美好的!
case 2: fully constrained view size, preserving image's aspect ratio
情况 2:完全约束视图大小,保留图像的纵横比
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!
在其他时候,你处于不同的情况。您确切地知道您希望图像视图采用的尺寸,但您还希望它保留所显示图像的纵横比。在这种情况下,您可以使用 Auto Layout 来约束图像的大小,同时在图像视图上设置旧contentMode
属性.scaleAspectFit
。该属性将导致图像视图重新缩放显示的图像,根据需要添加填充。如果这就是你想要的,生活就是美好的!
case 3: partially constrained view size, adapting to image aspect ratio
case 3:部分约束视图大小,适应图像纵横比
But often times, you're in the tricky third case. You want to use Auto Layout to partiallydetermine 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.
您不能配置普通图像视图来执行此操作,因为图像视图仅传达其intrinsicContentSize
. 因此,如果您将图像视图放在自动布局约束一维的上下文中(例如,将其约束为较短的宽度),那么图像视图不会告诉自动布局它希望其高度同时重新缩放。它继续报告其内在内容大小,图像本身的高度未修改。
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:
为了配置图像视图以告诉自动布局它想要采用它包含的图像的纵横比,您需要为该效果添加一个新约束。此外,每当更新图像本身时,您都需要更新该约束。您可以构建一个UIImageView
执行此操作的子类,因此该行为是自动的。下面是一个例子:
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
更多评论可在此要点中找到
回答by xirix
For @algal case 3, all I had to do was
对于@algal 案例 3,我所要做的就是
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.
这将缩放内在内容大小的高度组件。(-1.0, -1.0) 在没有图像时返回,因为这是超类中的默认行为。
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
.
另外,我不需要为UITableViewAutomaticDimension
包含图像视图的单元格设置表格,并且单元格仍然会自动调整大小。图像视图的内容模式是Aspect Fit
.
回答by Alexander Abakumov
Here is an AspectFit
UIImageView
-derived implementation that works with Auto Layout when only one dimension is constrained. The other one will be set automatically. It will keep image aspect ratio and doesn't add any margins around the image.
这是一个AspectFit
UIImageView
派生的实现,当只有一个维度受到约束时,它可以与自动布局一起使用。另一个将自动设置。它将保持图像纵横比,并且不会在图像周围添加任何边距。
It's tweaked Objective-C implementation of @algal ideawith the following differences:
它调整了@algal 想法的Objective-C 实现,具有以下差异:
- Expression
priority = (UILayoutPriorityDefaultLow + UILayoutPriorityFittingSizeLevel) / 2.0
which evaluates to150
isn't enough to beat the priority of1000
of the default image content size constraint. So aspect constraint priority was increased to1000
as well. - it removes\re-adds the aspect ratio constraint only if necessary.
Keep in mind that this is heavy operation so there is no need to do
so if aspect ratio is the same. Moreover,
image
setter could be called multiple times, so it's a good idea to not cause extra layout calculations here. - not sure why we need to override
required public init?(coder aDecoder: NSCoder)
andpublic override init(frame:CGRect)
, so these two are taken out. They aren't overridden byUIImageView
, that's why there is no point of adding the aspect constraint until an image is set.
priority = (UILayoutPriorityDefaultLow + UILayoutPriorityFittingSizeLevel) / 2.0
评估为的表达式150
不足以击败1000
默认图像内容大小约束的优先级。所以方面约束优先级也增加到了1000
。- 它仅在必要时删除\重新添加纵横比约束。请记住,这是一项繁重的操作,因此如果纵横比相同,则无需这样做。此外,
image
setter 可能会被多次调用,因此最好不要在此处进行额外的布局计算。 - 不知道为什么我们需要覆盖
required public init?(coder aDecoder: NSCoder)
andpublic override init(frame:CGRect)
,所以去掉了这两个。它们不会被 覆盖UIImageView
,这就是为什么在设置图像之前没有必要添加方面约束。
AspectKeepUIImageView.h
:
AspectKeepUIImageView.h
:
NS_ASSUME_NONNULL_BEGIN
@interface AspectKeepUIImageView : UIImageView
- (instancetype)initWithImage:(nullable UIImage *)image;
- (instancetype)initWithImage:(nullable UIImage *)image highlightedImage:(nullable UIImage *)highlightedImage;
@end
NS_ASSUME_NONNULL_END
AspectKeepUIImageView.m
:
AspectKeepUIImageView.m
:
#import "AspectKeepUIImageView.h"
@implementation AspectKeepUIImageView
{
NSLayoutConstraint *_aspectContraint;
}
- (instancetype)initWithImage:(nullable UIImage *)image
{
self = [super initWithImage:image];
[self initInternal];
return self;
}
- (instancetype)initWithImage:(nullable UIImage *)image highlightedImage:(nullable UIImage *)highlightedImage
{
self = [super initWithImage:image highlightedImage:highlightedImage];
[self initInternal];
return self;
}
- (void)initInternal
{
self.contentMode = UIViewContentModeScaleAspectFit;
[self updateAspectConstraint];
}
- (void)setImage:(UIImage *)image
{
[super setImage:image];
[self updateAspectConstraint];
}
- (void)updateAspectConstraint
{
CGSize imageSize = self.image.size;
CGFloat aspectRatio = imageSize.height > 0.0f
? imageSize.width / imageSize.height
: 0.0f;
if (_aspectContraint.multiplier != aspectRatio)
{
[self removeConstraint:_aspectContraint];
_aspectContraint =
[NSLayoutConstraint constraintWithItem:self
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:self
attribute:NSLayoutAttributeHeight
multiplier:aspectRatio
constant:0.f];
_aspectContraint.priority = UILayoutPriorityRequired;
[self addConstraint:_aspectContraint];
}
}
@end
回答by fl034
I used @algal's excellent answer(case #3) and improved it for my use case.
我使用了@algal 的优秀答案(案例#3)并针对我的用例进行了改进。
I wanted to be able to set min and max ratio constraints. So if I have a minimum ratio constraint of 1.0 and set a picture that is higher than wide, the size should be square.
Also it should fill the UIImageView
in those cases.
我希望能够设置最小和最大比率约束。因此,如果我将最小比率约束设置为 1.0 并设置高于宽度的图片,则大小应为正方形。UIImageView
在这些情况下,它也应该填写。
This works even in Interface Builder. Just add a UIImageView
, set RatioBasedImageView
as custom class in the identity inspector and set the max and min aspect ratios in the attributes inspector.
这甚至在 Interface Builder 中也有效。只需在身份检查器中添加一个UIImageView
, 设置RatioBasedImageView
为自定义类,并在属性检查器中设置最大和最小纵横比。
Here's my code.
这是我的代码。
@IBDesignable
public class RatioBasedImageView : UIImageView {
/// constraint to maintain same aspect ratio as the image
private var aspectRatioConstraint:NSLayoutConstraint? = nil
// This makes it use the correct size in Interface Builder
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.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)
constraint.priority = .required
self.addConstraint(constraint)
self.aspectRatioConstraint = constraint
}
}
}
回答by Vishal Singh
I usually add no image height and width constraints with value 0
and priority
less than UIImageView
's contentCompressionResistancePriority
, which by default is 750
. These two constraints will activate only when there is no image, and will take care of ambiguous constraints warnings both at run time and compile time.
我通常不添加带有 value0
和priority
小于UIImageView
's 的图像高度和宽度约束,contentCompressionResistancePriority
默认情况下是750
. 这两个约束只有在没有图像时才会激活,并且会在运行时和编译时处理不明确的约束警告。
Pretty straight forward to add these in IB. Programatically, it should look something like below.
在 IB 中添加这些非常简单。以编程方式,它应该如下所示。
let noImageWidthConstraint = imageview.widthAnchor.constraint(equalToConstant: 0)
noImageWidthConstraint.priority = UILayoutPriority(rawValue: 100)
noImageWidthConstraint.isActive = true
let noImageHeightConstraint = imageview.heightAnchor.constraint(equalToConstant: 0)
noImageHeightConstraint.priority = UILayoutPriority(rawValue: 100)
noImageHeightConstraint.isActive = true
回答by Duncan C
If you read the docs on UIImageView they say:
如果您阅读 UIImageView 上的文档,他们会说:
Setting the image property does not change the size of a UIImageView. Call sizeToFitto adjust the size of the view to match the image.
设置图像属性不会改变 UIImageView 的大小。调用sizeToFit来调整视图的大小以匹配图像。