Подтвердить что ты не робот

С автоматической компоновкой, как мне сделать динамический размер UIImageView в зависимости от изображения?

Я хочу, чтобы мой UIImageView увеличивался или сокращался в зависимости от размера того, что представляет собой фактическое изображение. Но я хочу, чтобы он оставался вертикально центрированным и 10pts от передней границы надзора.

Однако, если я устанавливаю эти два ограничения, они жалуются, что я не установил достаточные ограничения, чтобы удовлетворить Auto Layout. Мне кажется, что я прекрасно описываю то, что хочу. Что мне не хватает?

4b9b3361

Ответ 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, чтобы настроить размер представления в соответствии с изображением.