Должно ли для contentView.translatesAutoResizingMaskToConstraints подкласса UICollectionViewCell быть установлено значение «false»?

TL; DR

Пытаясь определить размер UICollectionViewCells с помощью автоматического макета, вы можете легко получить предупреждения автоматического макета даже на простом примере.

Должны ли мы устанавливатьcontentView.translatesAutoResizingMaskToConstraints = false избавиться от них?

Я пытаюсь создать UICollectionView савтоматическая разметка ячеек.

В viewDidLoad:

let layout = UICollectionViewFlowLayout()
layout.estimatedItemSize = CGSize(width: 10, height: 10)
collectionView.collectionViewLayout = layout

Моя клетка очень простая. Это единственный синий вид с шириной и высотой 75 для тестирования. Ограничения создаются путем закрепления вида в суперпредставлении на всех 4 ребрах и определения его высоты и ширины.

class MyCell: UICollectionViewCell {
  override init(frame: CGRect) {
    view = UIView()

    super.init(frame: frame)

    view.backgroundColor = UIColor.blueColor()
    contentView.addSubview(view)

    installConstraints()
  }

  required init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
  }

  var view: UIView

  func installConstraints() {
    view.translatesAutoresizingMaskIntoConstraints = false

    var c: NSLayoutConstraint

    // pin all edges
    c = NSLayoutConstraint(item: contentView, attribute: .Leading, relatedBy: .Equal, toItem: view, attribute: .Leading, multiplier: 1, constant: 0)
    c.active = true
    c = NSLayoutConstraint(item: contentView, attribute: .Trailing, relatedBy: .Equal, toItem: view, attribute: .Trailing, multiplier: 1, constant: 0)
    c.active = true
    c = NSLayoutConstraint(item: contentView, attribute: .Top, relatedBy: .Equal, toItem: view, attribute: .Top, multiplier: 1, constant: 0)
    c.active = true
    c = NSLayoutConstraint(item: contentView, attribute: .Bottom, relatedBy: .Equal, toItem: view, attribute: .Bottom, multiplier: 1, constant: 0)
    c.active = true

    // set width and height
    c = NSLayoutConstraint(item: view, attribute: .Width, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: 75)
    c.active = true
    c = NSLayoutConstraint(item: view, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: 75)
    c.active = true
  }
}

При выполнении кода я получаю две ошибки о невозможности одновременно удовлетворить ограничения:

Unable to simultaneously satisfy constraints.
    Probably at least one of the constraints in the following list is one you don't want. 
    Try this: 
        (1) look at each constraint and try to figure out which you don't expect; 
        (2) find the code that added the unwanted constraint or constraints and fix it. 
    (Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you don't understand, refer to the documentation for the UIView property translatesAutoresizingMaskIntoConstraints) 
(
    "<NSAutoresizingMaskLayoutConstraint:0x7fed88c1bac0 h=--& v=--& H:[UIView:0x7fed8a90c2f0(10)]>",
    "<NSLayoutConstraint:0x7fed8ab770b0 H:[UIView:0x7fed8a90bbe0(75)]>",
    "<NSLayoutConstraint:0x7fed8a90d610 UIView:0x7fed8a90c2f0.leading == UIView:0x7fed8a90bbe0.leading>",
    "<NSLayoutConstraint:0x7fed8ab005f0 H:[UIView:0x7fed8a90bbe0]-(0)-|   (Names: '|':UIView:0x7fed8a90c2f0 )>"
)

Will attempt to recover by breaking constraint 
<NSLayoutConstraint:0x7fed8ab770b0 H:[UIView:0x7fed8a90bbe0(75)]>

Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger.
The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKit/UIView.h> may also be helpful.

(Вторая ошибка о вертикальных ограничениях опущена, но она почти идентична.)

Эти ошибки имеют смысл. По сути, contentView (0x7fed8a90c2f0) имеетtranslatesAutoresizingMask = trueтаким образом, его границы, установленные на 10x10 (от предполагаемого размера), создают, чтоNSAutoresizingMaskLayoutConstraint ограничение. Нет никакой возможности, чтобы представление могло иметь ширину 10 и 75 пикселей, поэтому выводит ошибку.

Я пробовал несколько разных вещей, чтобы решить эту проблему, и только 2 из них, похоже, работали.

Решение 1. Установите хотя бы одно из ограничений на 999 или меньше

В этом случае, если я изменю ограничение, которое прикрепляет нижнюю часть синего представления к основанию contentView с приоритетом 999 (и делаю то же самое с конечным ограничением), это решает проблему. Точно так же я мог бы выбрать Top + Leading или Width + Height, если у меня нет 1000 приоритетов в каждом направлении. По крайней мере, один должен «дать» для первоначального макета.

Хотя это может звучать так, что представление будет иметь неправильную высоту / ширину, но на самом деле это правильный размер.

Решение 2: УстановитьcontentView.translatesAutoresizingMaskIntoConstraints = false

Установив это наfalseпо сути избавляется отNSAutoresizingMaskLayoutConstraint ограничение, и у нас больше нет конфликта. С другой стороны, так как contentView не настроен с автоматическим макетом для начала, как будто независимо от того, как вы изменяете его кадр, его суперпредставление (ячейка) никогда не будет обновлять свой размер, потому что нет ничего "проталкивающего" назад против этого.

Тем не менее, это решение каким-то волшебным образом работает. Я предполагаю, что в какой-то момент ячейка смотрит на фрейм contentView и решает «эй, вы установили мне этот размер, это, вероятно, означает, что вы также хотите, чтобы ячейка была такого же размера», и позаботится об изменении размера ячейки для вас.

Какое решение мы должны использовать?

Есть ли лучшие решения этой проблемы? Что из перечисленного следует использовать? Есть ли недостатки двух упомянутых выше решений, или они по сути одинаковы, и не имеет значения, какое вы используете?

Заметки:

Причину сложнее увидеть в таких примерах, какDGSelfSizingCollectionViewCells потому что они полагаются на intrinsicContentSize + contentCompressionResistancePriority. Если вы избавитесь от моих ограничений ширины и высоты, замените их на 1000 вызовов setContentCompressionResistancePriority (горизонтальный + вертикальный) и будете использовать что-то с внутренним размером (например, метку с текстом), вы больше не увидите ошибок автоматического макета. Это подразумевает, что[email protected] ведет себяпо-другому чем внутренняя ширина +[email protected], Возможно, время, в течение которого вид получает свою собственную ширину, - это когда все нормально, чтобы изменить рамку представления содержимого.

Вещи, которые не работали:

Я заметил, что когда я создаю ячейки через раскадровку, я обычно никогда не сталкиваюсь с этой проблемой. Когда я исследовал различия вview ячейки, созданной с помощью раскадровки, и одной, созданной программно, я заметил, чтоautoresizingMask было RM + BM (36) в раскадровке, но 0 при создании с помощью кода. Я пытался вручную установитьautoresizingMask моего голубого зрения до 36, но это не сработало. Реальная причина, по которой работает решение раскадровки, заключается в том, что обычно создаваемые вами ограничения уже отлично установлены в раскадровке (в противном случае у вас возникнут ошибки раскадровки, которые необходимо исправить). Как часть исправления этих ошибок, вы можете изменить границы ячейки. Так как это вызываетinit?(coder:)границы ячейки уже настроены правильно. Так что, хотяcontentView.autoresizingMask = trueнет никаких конфликтов, так как он уже определен в правильном размере.В связанном руководстве он ссылается наэта почта когда говорим о создании ограничений. В нем упоминается, что ограничения должны быть созданы вupdateConstraints, Тем не менее, похоже, что это были оригинальные рекомендации Apple, и что онибольше не рекомендую это соглашение для большинства ваших ограничений. Тем не менее, так как эта статья говорит, чтобы создать их вupdateConstraints Я пытался безрезультатно. Я получил те же результаты. Это связано с тем, что кадр не был обновлен ко времени вызова updateConstraints (он все еще был 10x10).Я заметил аналогичные проблемы, когда вы выбираете слишком маленький примерный размер. Я исправил это в прошлом, увеличив приблизительный размер. В этом случае я пробовал 100x100, но это все равно вызывало те же ошибки. Это имеет смысл ... представление не может быть одновременно шириной 100 и шириной 75. (Обратите внимание, что я считаю, что установка 75x75 не является решением. См. Ниже для получения дополнительной информации.)

Non-решения:

Установка приблизительного размера 75x75. Хотя это устранит ошибки для этого простого примера, это не решит проблему для ячеек, которые действительно имеют динамический размер. Я хочу решение, которое будет работать везде.Возврат точных размеров ячейки вcollectionView(_:layout:sizeForItemAtIndexPath:) (например, путем создания ячейки калибровки). Я хочу решение, которое работает с моей ячейкой напрямую, без необходимости каких-либо бухгалтерских или дополнительных расчетов.
 Cosyn17 авг. 2016 г., 11:38
Я обнаружил, что проблема исчезает, когда предполагаемый размер превышает размер, определенный автоматическим макетом.

Ответы на вопрос(1)

Решение Вопроса

contentView.translatesAutoresizingMaskToConstraints = true.

Если вы используетеpreferredLayoutAttributesFittingAttributes(_:) изменить размерыUICollectionViewCell, он работает путем определения размера содержимого в правильном измерении. Если вы установитеcontentView.translatesAutoresizingMaskToConstraints = falseВы потеряете эту функциональность.

Поэтому я рекомендую использовать Решение 1 (изменение по крайней мере одного ограничения в каждом измерении, чтобы быть необязательным). Фактически, я создал оболочку для UICollectionViewCell, которая будет обрабатывать как необходимые ограничения 999, так и способ получения предпочтительной высоты или ширины для правильного функционирования.

Используя эту оболочку, вам не нужно будет запоминать тонкости получения содержимого viewView в UICollectionViewCell для правильной работы.

class CollectionViewCell<T where T: UIView>: UICollectionViewCell {

  override init(frame: CGRect) {
    preferredHeight = nil
    preferredWidth = nil

    super.init(frame: frame)
  }

  var preferredWidth: CGFloat?
  var preferredHeight: CGFloat?
  private(set) var view: T?

  func initializeView(view: T) {
    assert(self.view == nil)
    self.view = view

    contentView.addSubview(view)

    view.translatesAutoresizingMaskIntoConstraints = false

    var constraint: NSLayoutConstraint

    constraint = NSLayoutConstraint(item: view, attribute: .Top, relatedBy: .Equal, toItem: contentView, attribute: .Top, multiplier: 1, constant: 0)
    constraint.active = true
    constraint = NSLayoutConstraint(item: view, attribute: .Leading, relatedBy: .Equal, toItem: contentView, attribute: .Leading, multiplier: 1, constant: 0)
    constraint.active = true

    // Priority must be less than 1000 to prevent errors when installing
    // constraints in conjunction with the contentView's autoresizing constraints.
    let NonRequiredPriority: UILayoutPriority = UILayoutPriorityRequired - 1

    constraint = NSLayoutConstraint(item: view, attribute: .Bottom, relatedBy: .Equal, toItem: contentView, attribute: .Bottom, multiplier: 1, constant: 0)
    constraint.priority = NonRequiredPriority
    constraint.active = true
    constraint = NSLayoutConstraint(item: view, attribute: .Trailing, relatedBy: .Equal, toItem: contentView, attribute: .Trailing, multiplier: 1, constant: 0)
    constraint.priority = NonRequiredPriority
    constraint.active = true
  }

  override func preferredLayoutAttributesFittingAttributes(layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
    let newLayoutAttributes = super.preferredLayoutAttributesFittingAttributes(layoutAttributes)

    if let preferredHeight = preferredHeight {
      newLayoutAttributes.bounds.size.height = preferredHeight
    }
    if let preferredWidth = preferredWidth {
      newLayoutAttributes.bounds.size.width = preferredWidth
    }

    return newLayoutAttributes
  }
}

(Примечание: метод init требуется из-заошибка с общими подклассами UICollectionViewCell.)

Зарегистрироваться:

collectionView.registerClass(CollectionViewCell<UIView>.self, forCellWithReuseIdentifier: "Cell")

Использовать:

func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
  let cell = collectionView.dequeueReusableCellWithReuseIdentifier("Cell", forIndexPath: indexPath) as! CollectionViewCell<UILabel>

  if cell.view == nil {
    cell.initializeView(UILabel())
  }

  cell.view!.text = "Content"
  cell.preferredHeight = collectionView.bounds.height

  return cell
}
 Eyzuky01 нояб. 2018 г., 16:19
Apple Docs говорит, что вам нужно вызвать super.preferredLayoutAttributesFitting .....

Ваш ответ на вопрос