OS X Cocoa Скрытые элементы Auto Layout
Я пытаюсь использовать новый Auto Layout в Lion, потому что это кажется довольно приятным. Но я не могу найти хорошую информацию о том, как это делать. Например:
У меня есть две метки:
+----------------+
| +------------+ |
| + label 1 | |
| +------------+ |
| |
| +------------+ |
| | label 2 | |
| +------------+ |
+----------------+
но первая метка не всегда заполняется содержимым, иногда нет для нее контента. То, что я хотел бы сделать, - это автоматически показать метку 2, где метка 1 будет, когда она будет иметь контент.
+----------------+
| +------------+ |
| + label 2 | |
| +------------+ |
| |
| |
| |
| |
+----------------+
Какие ограничения я должен добавить, чтобы он автоматически работал с автозапуском? Я понимаю, что я мог бы просто закодировать все, но у меня около 30 таких ярлыков и изображений и кнопок разных стилей и форм, которые являются необязательными, и я не хочу добавлять hundrets строк кода, когда он может работать автоматически довольно хорошо.
Если он не работает, я просто использую WebView и делаю это с помощью HTML и CSS.
Ответы
Ответ 1
Я не думаю, что ты мог бы так поступить. Если вы сделали макет для метки 2 основанной на дистанционном ограничении от метки 1, даже если вы сделали автоматическое сведение метки 1 к нулевой высоте, когда у нее нет содержимого, метка 2 все равно будет находиться на этом расстоянии, то есть в:
+----------------+
| +------------+ |
| + label 1 | |
| +------------+ |
| ^ |
| ^ !
| +------------+ |
| | label 2 | |
| +------------+ |
+----------------+
Где ^ - ограничение расстояния автоопределения. Если Label 1 знает, как стать нулевой высотой, когда строка пуста, вы все равно получите:
+----------------+
| +------------+ |
| ^ |
| ^ !
| +------------+ |
| | label 2 | |
| +------------+ |
+----------------+
Возможно, это возможно, создав NSLayoutConstraint вручную. Вы можете сделать второй атрибут высотой метки 1, сделать константу ноль, а затем тщательно разобраться, какой множитель должен был сделать расстояние, которое вы хотите, на основе кратного высоты ненулевой метки.
Но, выполнив все это, вы теперь закодировали подкласс NSLabel, который автоматически задает размер, создавал объект ограничения вручную, а не через визуальный язык, и сгибал NSLayoutConstraint за его пределами.
Я думаю, вам лучше просто изменить рамку метки 2, если строка ярлыка 1 пуста!
Ответ 2
Это возможно с автоматической компоновкой, но не очень хорошо масштабируется.
Итак, возьмем ваш пример, допустим, у вас есть метка A и метка B (или кнопка или что-то еще на самом деле). Сначала начните с добавления верхнего ограничения в супервизор для A. Затем установите ограничение по вертикали между A и B. Это пока нормально. Если вы должны были удалить A в этот момент, B будет иметь двусмысленный макет. Если вы должны были скрыть это, он все равно будет занимать это пространство, включая пробел между метками.
Затем вам нужно добавить еще одно ограничение из B в начало супервизора. Измените приоритет на этом, чтобы быть ниже остальных (скажем 900), а затем установите его постоянным как стандартное (или другое меньшее значение). Теперь, когда A будет удалено из него, верхнее ограничение будет вбиваться и тянуть B в направлении вверх. Ограничения выглядят примерно так:
Скриншот интерфейса Builder http://f.cl.ly/items/2J120b3Q3o2u1H1z3b2T/Screen%20Shot%202012-08-16%20at%2010.40.28%20PM.png
Проблема возникает, когда вы пытаетесь сделать это с длинным списком меток.
Ответ 3
Скрывающий подкласс UILabel
Одним простым решением является просто подкласс UILabel и изменение внутреннего содержимого.
@implementation WBSCollapsingLabel
- (CGSize)intrinsicContentSize
{
if (self.isHidden) {
return CGSizeMake(UIViewNoIntrinsicMetric, 0.0f);
} else {
return [super intrinsicContentSize];
}
}
- (void)setHidden:(BOOL)hidden
{
[super setHidden:hidden];
[self updateConstraintsIfNeeded];
[self layoutIfNeeded];
}
@end
Ответ 4
Эта категория упрощает свертывание автоматических макетов с ограниченным представлением:
https://github.com/depth42/AutolayoutExtensions
Я просто добавил его в проект, и он отлично работает.
Ответ 5
Вот пример того, как я обрабатывал это программно, а не используя Interface Builder. В итоге; Я добавляю только представление, если оно включено, а затем перебирает подзоны, добавляя вертикальные ограничения, когда я иду.
Обратите внимание, что рассматриваемые представления инициализируются до этого.
/*
Begin Auto Layout
*/
NSMutableArray *constraints = [NSMutableArray array];
NSMutableDictionary *views = [[NSMutableDictionary alloc] init];
/*
Label One
*/
if (enableLabelOne) {
[contentView addSubview:self.labelOne];
self.labelOne.translatesAutoresizingMaskIntoConstraints = NO;
[views setObject:self.labelOne
forKey:@"_labelOne"];
[constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[_labelOne(44)]"
options:0
metrics:nil
views:views]];
[constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[_labelOne]-|"
options:0
metrics:nil
views:views]];
}
/*
Label Two
*/
if (enableLabelTwo) {
[contentView addSubview:self.labelTwo];
self.labelTwo.translatesAutoresizingMaskIntoConstraints = NO;
[views setObject:self.labelTwo
forKey:@"_labelTwo"];
[constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[_labelTwo(44)]"
options:0
metrics:nil
views:views]];
}
[constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[_labelTwo]-|"
options:0
metrics:nil
views:views]];
/*
Dynamically add vertical spacing constraints to subviews
*/
NSArray *subviews = [contentView subviews];
if ([subviews count] > 0) {
UIView *firstView = [subviews objectAtIndex:0];
UIView *secondView = nil;
UIView *lastView = [subviews lastObject];
[constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-[firstView]"
options:0
metrics:nil
views:NSDictionaryOfVariableBindings(firstView)]];
for (int i = 1; i < [subviews count]; i++) {
secondView = [subviews objectAtIndex:i];
[constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[firstView]-10-[secondView]"
options:0
metrics:nil
views:NSDictionaryOfVariableBindings(firstView, secondView)]];
firstView = secondView;
}
[constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[lastView]-|"
options:0
metrics:nil
views:NSDictionaryOfVariableBindings(lastView)]];
}
[self addConstraints:constraints];
Я устанавливаю ограничение lastView
, потому что этот код был адаптирован из чего-то внутри UIScrollView.
Первоначально я реализовал это на основе этого ответа на переполнение стека и изменил его в соответствии с моими потребностями.
Ответ 6
Я нашел по-видимому достойный способ сделать это. Он похож на Дэвида. Вот как это работает в коде. Я создал супервизор и все его подпункты, даже те, которые могут не всегда отображаться. Я добавил множество ограничений, таких как V:|-[_btn]
в супервизор. Как вы можете видеть, в конце этих ограничений нет ссылки на нижнюю часть надстройки. Затем я создал два массива ограничений для обоих состояний представления, для меня разница - это треугольник раскрытия "Дополнительные параметры". Затем, когда нажимается треугольник в зависимости от состояния, я добавляю и удаляю ограничения и подсмотры соответственно. Например, чтобы добавить:
[self.backgroundView removeConstraints:self.lessOptionsConstraints];
[self.backgroundView addSubview:self.nameField];
[self.backgroundView addConstraints:self.moreOptionsConstraints];
Ограничения, которые я удалил, привязали кнопку к нижней части супервизора, как V:[_btn]-|
. Добавленные ограничения выглядят как V:[_btn]-[_nameField]-|
, так как вы можете видеть, что это ограничение помещает новое представление между исходным представлением над ним и нижней частью супервизора, которая увеличивает высоту надписи.
Ответ 7
Я нашел другой способ сделать это. Эта методология может применяться везде, не имеет проблем с масштабированием; и обрабатывает поля также. И вам не нужны сторонние вещи для этого.
Во-первых, не используйте этот макет:
V:|-?-[Label1]-10-[Label2]-10-|
H:|-?-[Label1]-?-|
H:|-20-[Label2]-20-|
Используйте их вместо:
("|" is the real (outer) container)
V:|-?-[Label1]-0-[Label2HideableMarginContainer]-0-|
H:|-?-[Label1]-?-|
H:|-0-[Label2HideableMarginContainer]-0-|
("|" is Label2HideableMarginContainer)
V:|-10-[Label2]-10-|
H:|-20-[Label2]-20-|
Итак, что мы сделали сейчас? Label2
напрямую не используется в макете; он помещается в Margin-Container
. Этот контейнер используется как прокси Label2
, с 0 полями в макете. Реальные поля помещаются внутри из Margin-Container
.
Теперь мы можем скрыть Label2
с помощью:
- настройка
Hidden
на YES
на нем
и
- Отключение ограничений
Top
, Bottom
, Leading
и Trailing
. Поэтому найдите их, а не установите Active
на NO
. Это приведет к тому, что Margin-Container
будет иметь значение Frame Size
(0,0); потому что у него есть subview (s); но нет никаких (активных) ограничений компоновки, которые привязывают к ним эти подпрограммы.
Может быть, немного сложнее, но вам нужно только развить его. Вся логика может быть помещена в отдельное место и использоваться каждый раз, когда вам нужно скрыть smg.
Вот код С# Xamarin, как искать те ограничения, которые привязывают subview (s) к внутренним ребрам представления Margin-Container
:
public List<NSLayoutConstraint> SubConstraints { get; private set; }
private void ReadSubContraints()
{
var constraints = View.Constraints; // View: the Margin-Container NSView
if(constraints?.Any() ?? false)
{
SubConstraints = constraints.Where((NSLayoutConstraint c) => {
var predicate =
c.FirstAttribute == NSLayoutAttribute.Top ||
c.FirstAttribute == NSLayoutAttribute.Bottom ||
c.FirstAttribute == NSLayoutAttribute.Leading ||
c.FirstAttribute == NSLayoutAttribute.Trailing;
predicate &= ViewAndSubviews.Contains(c.FirstItem); // ViewAndSubviews: The View and View.Subviews
predicate &= ViewAndSubviews.Contains(c.SecondItem);
return predicate;
}).ToList();
}
}
Ответ 8
Решена проблема программно. У меня есть несколько кнопок подряд, и я могу решить скрыть их в любое время.
![введите описание изображения здесь]()
Используется Cartography, чтобы заменять их каждый раз, когда hidden
изменяется в любом из них.
let buttons = self.buttons!.filter { button in
return !button.hidden
}
constrain(buttons, replace: self.constraintGroup) { buttons in
let superview = buttons.first!.superview!
buttons.first!.left == superview.left
for var i = 1; i < buttons.count; i++ {
buttons[i].left == buttons[i-1].right + 10
}
buttons.last!.right == superview.right
}