С автоматической компоновкой, как мне сделать динамический размер UIImageView в зависимости от изображения?
Я хочу, чтобы мой UIImageView
увеличивался или сокращался в зависимости от размера того, что представляет собой фактическое изображение. Но я хочу, чтобы он оставался вертикально центрированным и 10pts от передней границы надзора.
Однако, если я устанавливаю эти два ограничения, они жалуются, что я не установил достаточные ограничения, чтобы удовлетворить Auto Layout. Мне кажется, что я прекрасно описываю то, что хочу. Что мне не хватает?
Ответы
Ответ 1
Внутренний размер представления изображения уже зависит от размера изображения. Ваши предположения (и ограничения верны).
Однако, если вы настроили представление изображения в построителе интерфейса и не предоставили его с изображением, тогда система макета (построитель интерфейса) не будет знать, насколько большой должен быть ваш просмотр изображения во время компиляции, Ваш макет неоднозначен, потому что ваше изображение может иметь много размеров. Это то, что выдает ошибки.
После того, как вы установите свойство изображения с изображением, внутренний размер изображения будет определяться размером изображения. Если вы устанавливаете представление во время выполнения, вы можете точно указать, что упомянула Анна, и предоставить конструктору интерфейса свой собственный размер "заполнителя" в инспекторе свойств изображения. Это говорит о построении интерфейса, "используйте этот размер на данный момент, я дам вам реальный размер позже". Ограничения placeholder игнорируются во время выполнения.
Другой вариант - напрямую назначить изображение в представлении изображения в построителе интерфейса (но я предполагаю, что ваши изображения являются динамическими, так что это не сработает для вас).
Ответ 2
Вот основная проблема: единственный способ, с помощью которого UIImageView
взаимодействует с Auto Layout, заключается в свойстве intrinsicContentSize
. Это свойство обеспечивает внутренний размер самого изображения и не более того. Это создает три ситуации с различными решениями.
случай 1: просмотр с размером изображения
В первом случае, если вы поместите UIImageView
в контексте, где внешние ограничения автоматического макета влияют только на его позицию, тогда представление intrinsicContentSize
объявит, что оно хочет быть размером с его изображением, а автоматический макет будет автоматически измените размер изображения, чтобы он равнялся размеру его изображения. Это иногда то, что вы хотите, и тогда жизнь хороша!
case 2: полностью ограниченный размер представления, сохраняя соотношение сторон изображения
В другое время вы находитесь в другом случае. Вы точно знаете размер, который требуется для просмотра изображений, но вы также хотите, чтобы он сохранял соотношение сторон отображаемого изображения. В этом случае вы можете использовать автоматическую компоновку, чтобы ограничить размер изображения, при установке старого свойства contentMode
на .scaleAspectFit
на изображении. Это свойство приведет к тому, что изображение начнет масштабировать отображаемое изображение, добавив добавку по мере необходимости. И если это то, что вы хотите, жизнь хороша!
случай 3: частично ограниченный размер представления, адаптация к формату изображения
Но часто время, вы в сложном третьем случае. Вы хотите использовать автоматическую компоновку, чтобы частично определить размер представления, позволяя при этом пропорции изображения определять другую часть. Например, вы можете использовать ограничения Auto Layout для определения одного измерения размера (например, ширины в вертикальной прокрутке по временной шкале), полагаясь на представление, чтобы сообщить Auto Layout, что он хочет, чтобы высота соответствовала соотношению сторон изображения ( поэтому прокручиваемые элементы не слишком короткие или слишком высокие).
Вы не можете настроить обычный образ, чтобы сделать это, потому что, поскольку просмотр изображения передает только его intrinsicContentSize
. Поэтому, если вы помещаете представление изображения в контекст, где Auto Layout ограничивает одно измерение (например, ограничивая его до короткой ширины), тогда изображение не сообщает Auto Layout, что он хочет, чтобы его высота масштабировалась в тандеме. Он продолжает сообщать о своем собственном размере контента с неизмененной высотой самого изображения.
Чтобы настроить просмотр изображения, чтобы сообщить автомакету, что он хочет принять соотношение сторон изображения, которое он содержит, вам нужно добавить новое ограничение для этого эффекта. Кроме того, вам нужно будет обновить это ограничение всякий раз, когда будет обновлено изображение. Вы можете создать подкласс 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
}
}
}
Подробнее о этот смысл
Ответ 3
Для случая @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)
}
}
Это масштабирует высоту компонента intrinsicContentSize. (-1.0, -1.0) возвращается, когда нет изображения, потому что это поведение по умолчанию в суперклассе.
Кроме того, мне не нужно было устанавливать UITableViewAutomaticDimension
для таблицы с ячейкой, содержащей вид изображения, и размер ячейки автоматически изменялся. Режим просмотра изображения - Aspect Fit
.
Ответ 4
Вот реализация AspectFit
UIImageView
-derived, которая работает с Auto Layout, когда ограничено только одно измерение. Другой будет установлен автоматически. Он сохранит пропорции изображения и не добавит никаких полей вокруг изображения.
Он изменил Objective-C реализацию идеи @algal со следующими отличиями:
- Выражение
priority = (UILayoutPriorityDefaultLow +
UILayoutPriorityFittingSizeLevel) / 2.0
, которое оценивается как 150
недостаточно, чтобы превзойти приоритет 1000
изображения по умолчанию
ограничение размера содержимого. Таким образом, приоритет ограничения приоритета был увеличен
до 1000
.
- он удаляет\повторно добавляет ограничение соотношения сторон только при необходимости.
Имейте в виду, что это тяжелая операция, поэтому нет необходимости делать
поэтому, если соотношение сторон одинаково. Более того, установщик
image
может быть вызван несколько раз, поэтому неплохо не вызывать дополнительные вычисления компоновки здесь.
- Не знаю, почему нам нужно переопределить
required public init?(coder
aDecoder: NSCoder)
и public override init(frame:CGRect)
, поэтому
эти два изъяты. Они не переопределяются UIImageView
,
поэтому нет смысла добавлять ограничение по размеру до тех пор, пока не будет установлено изображение.
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
:
#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
Ответ 5
Я использовал @algal превосходный ответ (вариант № 3) и улучшил его для своего варианта использования.
Я хотел иметь возможность устанавливать ограничения минимального и максимального отношения. Так что, если у меня есть ограничение минимального отношения 1,0 и я устанавливаю изображение, которое больше, чем широко, размер должен быть квадратным.
Также он должен заполнить UIImageView
в этих случаях.
Это работает даже в Интерфейсном Разработчике. Просто добавьте UIImageView
, установите RatioBasedImageView
в качестве пользовательского класса в инспекторе идентичности и установите максимальное и минимальное соотношение сторон в инспекторе атрибутов.
Вот мой код.
@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
}
}
}
Ответ 6
Если вы читаете документы в UIImageView, они говорят:
Настройка свойства изображения не изменяет размер UIImageView. Вызовите sizeToFit, чтобы настроить размер представления в соответствии с изображением.