Автоматический макет с относительными ограничениями, не влияющими на systemLayoutSizeFittingSize: для UITableViewCell

У меня есть UITableView с динамической высотой UITableViewCell, и я использую автоматический макет для вычисления высот по этому отличному ответу по этой теме. Пока все хорошо.

Я адаптирую приложение для работы с более крупными размерами экрана iPhone (6 и 6 Plus), и это в основном довольно просто.

Однако в некоторых моих ячейках у меня есть изображение, которое я хочу охватить всю ширину ячейки, и я хочу, чтобы высота изображения была пропорциональна ширине изображения (0,55 * ширина, чтобы быть конкретным). До сих пор у меня была ширина и высота изображения, жестко закодированные в ограничениях автоматической компоновки, в зависимости от стандартной высоты стола для настольного стола 320 пикселей, до iPhone 6/6 Plus.

Я думал, что было бы просто добавить относительное ограничение высоты, подобное этому (я использую PureLayout):

[self.myImage autoMatchDimension:ALDimensionHeight 
                     toDimension:ALDimensionWidth 
                          ofView:self 
                  withMultiplier:0.55f 
                        relation:NSLayoutRelationGreaterThanOrEqual];

Если вы не знакомы с PureLayout, это переводит на вызов

[NSLayoutConstraint constraintWithItem: attribute: relatedBy: toItem: attribute: multiplier: constant:0.0f]

Существуют и другие ограничения, которые привязывают его к супервину, который является UITableViewCell contentView.

Однако, когда я вызываю systemLayoutSizeFittingSize: в ячейке contentView, он, похоже, полностью игнорирует относительное ограничение по высоте, а результирующая высота ячейки слишком мала, чтобы соответствовать изображению.

Если я устанавливаю явное ограничение по высоте вместо относительного, нет проблем. Аналогично, если я подклассом UIImageView и возвращаю явный размер в intrinsicContentSize, проблем нет.

Я даже попробовал следующее в подклассе UIImageView:

- (void) layoutSubviews {
    [super layoutSubviews];    
    self.intrinsicSizeForAutolayout = self.frame.size;    
    [super layoutSubviews];
}

- (CGSize)intrinsicContentSize {
    return self.intrinsicSizeForAutolayout;
}

где intrinsicSizeForAutolayout - свойство, которое я определил для этой цели. Я думал, что это может работать аналогично тому, как настройка preferredMaxLayoutWidth для UILabel решает аналогичную проблему.

Но нет. Это не работает.

Казалось бы, у меня есть небольшая альтернатива использовать уродливый код проверки ширины экрана, чтобы условно установить фиксированное ограничение высоты в зависимости от ширины экрана, чего я действительно хотел избежать, так как это как бы побеждает цель использования автоматической компоновки в первое место.

Ответ 1

Ширина ячейки contentView по умолчанию не имеет ограничений (ее ширина устанавливается SDK только тогда, когда она добавляется в представление таблицы), поэтому, когда вы вызываете systemLayoutSizeFittingSize: на ней, ограничение solver предполагает, что он имеет смысл сжимать ширину столько, сколько необходимо при попытке найти правильное решение, что, конечно, приводит к неправильной высоте.

Чтобы исправить это, вы можете добавить единственное ограничение в contentView, которое фиксирует его ширину до последней ширины представления ячейки/таблицы. Это работает, потому что это ограничение будет учитываться в пропуске макета ячейки и приводит к работе systemLayoutSizeFittingSize:, как ожидалось.

Используя PureLayout, я рекомендую сделать что-то вроде:

[UIView autoSetPriority:UILayoutPriorityRequired - 1 forConstraints:^{
    [cell.contentView autoSetDimension:ALDimensionWidth toSize:CGRectGetWidth(tableView.bounds)];
}];

Обратите внимание, что это не плохая идея установить приоритет меньше, чем требуется для ограничения, просто потому, что это просто помогает выполнить операцию калибровки, и вы не хотите, чтобы исключение было нарушено (возможно, в будущем Изменение SDK в том, как работают ячейки таблицы). Но это, вероятно, не имеет значения.

Кроме того, как обычно, вы, вероятно, захотите убедиться, что это ограничение добавляется только один раз (не каждый раз при вызове tableView:heightForRowAtIndexPath:) - это должно быть легко сделать.

См. здесь конкретное решение для вашей вилки проекта примера: https://gist.github.com/smileyborg/0a2082a4d26fcc7fde4d