Использование AutoLayout для стека в двух столбцах разных высот
Targeting iOS 8.1
Я использую AutoLayout, чтобы выложить несколько ярлыков в TableCell. Некоторые из этих ярлыков являются необязательными, а некоторые могут обернуть их текст. Они разделены на два столбца "Столбцы", эти столбцы представляют собой два UIView в TableCell ContentView. Мои ограничения применяются программно.
ВТОРОЕ ОБНОВЛЕНИЕ
Без Ответ SwiftArchitect ниже Я бы не решил этого и принял его ответ. Однако, поскольку мой все в коде, в пользовательской таблице, я также добавил отдельный ответ ниже
UPDATE
В попытке остановить ярлыки от растяжения до размера, большего, чем нужно, я ранее устанавливал значения SetContentHuggingPriority
и SetContentCompressionResistancePriority
в 1000, поскольку я полагал, что это было эквивалентно: "Я хочу, чтобы метка была обнимайте его содержимое до его точной высоты, и я не хочу, чтобы он когда-либо был сжат по вертикали"
Этот запрос явно не соответствовал AutoLayout, как вы можете видеть в примерах Red и Pink ниже.
this.notesLabel.SetContentHuggingPriority(1000, UILayoutConstraintAxis.Vertical);
this.notesLabel.SetContentCompressionResistancePriority(1000, UILayoutConstraintAxis.Vertical);
Я удалил настройку этих приоритетов, и ярлыки больше не раздавлены, что было моей оригинальной проблемой. Конечно, теперь некоторые метки растягиваются за пределами высоты, в которой они должны быть.
- Почему удаление приоритетов Hugging and Compression устраняет мои
вопрос?
- Как я могу получить текст в красном поле (красное поле, не входящее в ячейку, добавленное позже), чтобы не расширяться, не возвращаясь к моей предыдущей проблеме?
![введите описание изображения здесь]()
Вот несколько скриншотов того, как это выглядело, когда приоритеты сжатия и обнимания устанавливаются. Цвета фона предназначены для отладки
![введите описание изображения здесь]()
Общая проблема заключалась в том, что Содержащий вид (цветной фиолетовый и красный) подбирал себя к меньшему из двух. Как вы можете видеть в верхней части, "Приоритет 3" сокращается, потому что левый контейнер столбца не должен быть выше.
В следующем примере нет метки приоритета, но EventDate сжимается.
![введите описание изображения здесь]()
Ответы
Ответ 1
Следующий ответ был написан и протестирован. Он работает правильно на iPhone и iPad, портрет и пейзаж. Побеждает самая высокая колонна, а другая просто занимает необходимое пространство. При необходимости он может быть изменен для вертикально центрирования объектов. Он затрагивает проблемы, связанные с вертикальной обрезкой, а также динамическое масштабирование.
![введите описание изображения здесь]()
Предварительный совет
- Используйте
Storyboard
, если можете. Вы можете проверить все свои ограничения визуально с помощью современного графического интерфейса.
-
UILabel
height: пусть каждая метка занимает пространство, которое требуется вертикально, и добавляет только верхние и боковые привязки
- Используйте дополнительные представления в качестве контейнеров для определения ширины каждого столбца. Используйте
multiplier
, чтобы получить, скажем 2/3 и 1 треть.
- Пусть эти представления вычисляют их идеальную высоту, добавляя единственное ограничение высоты к нижней части нижней метки (leftColumn.bottom Equal lowerLeftLabel.bottom)
- Не добавлять и не удалять представления динамически; скорее, скрыть их, чтобы они сохраняли связанные ограничения.
Описание решения
Для простоты я создал 2 подпункта, 1 для каждого столбца и расположил их бок о бок. Они привязаны сверху/слева и сверху/вправо, их ширина вычисляется, а их высота получается из их соответствующего содержимого (*).
В левом и правом подзонах есть 1/2 multiplier
, к которому я добавил a constant
из 2 пикселей для поля. Метки внутри этих двух столбцов привязаны слева и справа (leading space
к контейнеру и trailing space
к контейнеру) с краем 8 пикселей. Это гарантирует, что ни одна этикетка никогда не истечет за ее колонку.
- Учтите, что высота вашего
UITableViewCell
является самой большой из двух внутренних столбцов. Другими словами, containerView.height >= left.height и контейнерView.height >= right.height.
- Убедитесь, что вы не удаляете какие-либо невидимые ярлыки.
view.hidden
не нарушит ваши ограничения, и это то, что вы хотите.
- Прикрепите каждый
UILabel
слева и справа к контейнеру, самый верхний к контейнеру, но каждый последующий label.top должен быть привязан к .bottom
одного над ним. Таким образом, ваш контент будет течь. Вы можете добавить поля, если хотите.
(*) Конечный ключ - связать высоту каждого столбца с ограничением на столбец, чтобы он равнялся .bottom
нижней метки для этого столбца. В приведенном выше примере вы можете четко видеть, что правый столбец с синим фоном короче левого.
![введите описание изображения здесь]()
Пока я вижу, что вам нужно решение в коде, я создал свой пример с помощью Storyboard
менее чем за 15 минут. Это не просто концепция, это реальная реализация. Он имеет ровно 0 строк кода и работает на всех устройствах iOS. Кстати, он также имеет 0 ошибок.
Список всех ограничений
Обратите внимание на то, что >= посыпано здесь и там. Они являются ключом к тому, чтобы ваши колонки были независимо высокими.
NSLayoutContraint
практически идентичны для L и R.
![введите описание изображения здесь]()
![введите описание изображения здесь]()
Получите раскадровку здесь и подробную статью там.
Ответ 2
Я принял ответ SwiftArchitect, но, увидев, как я был после подхода на основе кода для TableCell, я добавлю отдельный ответ. Без его помощи я
не смог бы зайти так далеко.
Я использую MVVMCross и Xamarin iOS, и мой TableCell наследует от MvxTableViewCell
Создание подпрограмм
Из ctor ячейки я создаю все необходимые UILabels и отключу AutoResizingMasks, установив view.TranslatesAutoresizingMaskIntoConstraints = false
В то же время я создаю два UIViews leftColumnContainer и rightColumnContainer. Они снова TranslatesAutoresizingMaskIntoConstraints
установлены в false.
Соответствующие метки добавляются как подпункты к leftColumnContainer
и rightColumnContainer
UIViews. Затем два контейнера добавляются в качестве подматрицы в TableCell ContentView
this.ContentView.AddSubviews(this.leftColumnContainer, this.rightColumnContainer);
this.ContentView.TranslatesAutoresizingMaskIntoConstraints = true;
UILabels все данные связаны через вызов MVVMCross DelayBind
Настройка ограничений компоновки (UpdateConstraints())
Макет TableCell зависит от данных для ячейки, где 5 из 8 меток являются необязательными, и 4 из 8, нуждающихся в поддержке обертывания текста
Первое, что я делаю, - это привязать верхнюю и левую части leftColumnContainer
к TableCell.ContentView. Затем верхний и правый из "rightColumnContainer" в TableCell.ContentView. Конструкция требует, чтобы правый столбец был меньше левого, поэтому это делается с помощью масштабирования. Я использую FluentLayout для применения этих ограничений
this.ContentView.AddConstraints(
this.leftColumnContainer.AtTopOf(this.ContentView),
this.leftColumnContainer.AtLeftOf(this.ContentView, 3.0f),
this.leftColumnContainer.ToLeftOf(this.rightColumnContainer),
this.rightColumnContainer.AtTopOf(this.ContentView),
this.rightColumnContainer.ToRightOf(this.leftColumnContainer),
this.rightColumnContainer.AtRightOf(this.ContentView),
this.rightColumnContainer.WithRelativeWidth(this.ContentView, 0.35f));
Вызовы ToLeftOf и ToRight of устанавливают правый край левого столбца и левый край правого столбца рядом друг с другом.
Ключевым элементом решения, которое пришло от SwiftArchitect, было установить высоту TableCell ContentView нa >= на высоту leftColumnContainer
и rightColumnContainer
. Было не так очевидно, как это сделать с FluentLayout, чтобы они были "длинными руками"
this.ContentView.AddConstraint(
NSLayoutConstraint.Create(this.ContentView, NSLayoutAttribute.Height, NSLayoutRelation.GreaterThanOrEqual, this.leftColumnContainer, NSLayoutAttribute.Height, 1.0f, 5.0f));
this.ContentView.AddConstraint(
NSLayoutConstraint.Create(this.ContentView, NSLayoutAttribute.Height, NSLayoutRelation.GreaterThanOrEqual, this.rightColumnContainer, NSLayoutAttribute.Height, 1.0f, 5.0f));
Затем я ограничиваю верхнюю, левую и правую стороны первой метки в каждом столбце контейнером столбца. Ниже приведен пример первой метки в левом столбце
this.leftColumnContainer.AddConstraints(
this.categoryLabel.AtTopOf(this.leftColumnContainer, CellPadding),
this.categoryLabel.AtRightOf(this.leftColumnContainer, CellPadding),
this.categoryLabel.AtLeftOf(this.leftColumnContainer, CellPadding));
Для каждой из ярлыков, которые являются необязательными, я сначала проверяю MVVMCross DataContext, чтобы увидеть, видны ли они. Если они видны, то аналогичные ограничения для Left, Top и Right применяются, когда Top ограничивается нижним знаком метки выше. Если они не видны, они удаляются из представления так
this.bodyText.RemoveFromSuperview();
Если вам интересно, как эти ячейки будут работать с iOSs Cell Reuse, я расскажу об этом далее.
Если метка будет последней меткой в столбце (это зависит от данных), я применяю другое ключевое обучение из ответа SwiftArcthiect
Пусть [столбцы] вычисляют их идеальную высоту, добавляя один ограничение высоты до нижней части нижней метки (leftColumn.bottom Равный lowLeftLabel.bottom)
Работа с повторным использованием ячеек
С таким сложным набором ограничений и многих дополнительных ячеек я не хотел бы повторно применять ограничения каждый раз, когда ячейка была повторно использована с потенциально разными необязательными ярлыками. Для этого я создаю и устанавливаю идентификатор повторного использования во время выполнения.
TableSource наследует MvxTableViewSource. В переопределенном GetOrCreateCellFor я проверяю конкретный идентификатор повторного использования (нормальное использование), и если это так
вызовите DequeueReusableCell, однако в этом случае я откладываю на процедуру, инкапсулированную в пользовательский класс ячеек, который знает, как построить идентификатор данных
protected override UITableViewCell GetOrCreateCellFor(UITableView tableView, NSIndexPath indexPath, object item)
{
MvxTableViewCell cell;
if (this.reuseIdentifier != null)
{
cell = (MvxTableViewCell)tableView.DequeueReusableCell(this.reuseIdentifier);
}
else
{
// No single reuse identifier, defer to the cell for the identifer
string identifier = this.itemCell.GetCellIdentifier(item);
if (this.reuseIdentifiers.Contains(identifier) == false)
{
tableView.RegisterClassForCellReuse(this.tableCellType, identifier);
this.reuseIdentifiers.Add(identifier);
}
cell = (MvxTableViewCell)tableView.DequeueReusableCell(identifier);
}
return cell;
}
и вызов для создания id
public string GetCellIdentifier(object item)
{
StringBuilder cellIdentifier = new StringBuilder();
var entry = item as EntryItemViewModelBase;
cellIdentifier.AppendFormat("notes{0}", entry.Notes.HasValue() ? "yes" : "no");
cellIdentifier.AppendFormat("_body{0}", !entry.Body.Any() ? "no" : "yes");
cellIdentifier.AppendFormat("_priority{0}", entry.Priority.HasValue() ? "yes" : "no");
cellIdentifier.AppendFormat("_prop1{0}", entry.Prop1.HasValue() ? "yes" : "no");
cellIdentifier.AppendFormat("_prop2{0}", entry.Prop2.HasValue() ? "yes" : "no");
cellIdentifier.AppendFormat("_warningq{0}", !entry.IsWarningQualifier ? "no" : "yes");
cellIdentifier.Append("_MEIC");
return cellIdentifier.ToString();
}
Ответ 3
Прежде всего, мы не должны играть вокруг остроконечного контента - приоритет обмана или сжатия, если не существует ситуации, когда вам нужно изменить без останова. Значение по умолчанию 250: 750, заданное яблоком, будет соответствовать 90% сценария, если настройка автоматически настроена.
Только в редких случаях, когда существует конфликт для механизма изменения размера представления на основе выполненных ограничений, мы должны изменить приоритеты обмана/сжатия.
Ваша оригинальная проблема - ваши ярлыки раздавлены.
Пометки по умолчанию позволяют использовать собственный размер содержимого системы, где двигатель будет определять ширину и высоту метки по умолчанию в зависимости от содержимого ярлыков и размера текста.
Поэтому, если вы решите, что ваш ярлык не расширяет представление, тогда вы должны установить ограничение на конец с правой стороны ярлыка на представление. В общем случае он будет установлен в свойство "Equals", которое не будет соответствовать нашему требованию, потому что наш ярлык передается по внутреннему свойству, и мы не должны предоставлять стандартную ширину метки. Следовательно, вместо "Равенство должно быть" больше или равно значению для конечного свойства.
В вашем сценарии вы не должны устанавливать ограничение высоты для меток и активировать функцию переноса слов вдоль 'с количеством свойств линии для любой требуемой метки двух строк.
вы знаете, что вам всегда нужно отображать "Желтая метка", "зеленая метка" и "фиолетовая метка" в правом боковом представлении, независимо от того, что слева отображается одна или две строки на "красной этикетке".
Так исправить статическую высоту для ячейки.
С правой стороны,
Исправьте верхнее ограничение для "оранжевой" метки и нижнее ограничение для "желтой метки". Таким образом, красная метка центра получит ясную высоту, которая может вместить одну/две строки в соответствии с требованием. И приложите достаточные ограничения для правильной стороны, чтобы выполнить ваши требования.
Если это не решит вас, вы не опубликуете или не обсудите мое решение, напишите ниже.