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]
ведет себя по-разному, чем intrinsicWidth +[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:)
(например, путем создания ячейки калибровки). Я хочу, чтобы решение, которое работает с моей ячейкой напрямую, не требует каких-либо бухгалтерских или дополнительных расчетов.