OS X Cocoa Auto Layout приховані елементи

Я намагаюся використовувати новий автоматичний компонування в леві, тому що це здається досить приємним. Але я не можу знайти хорошу інформацію про те, як це робити. Наприклад:

У мене є дві мітки:

+----------------+
| +------------+ |
| + label 1    | |
| +------------+ |
|                |
| +------------+ |
| | label 2    | |
| +------------+ |
+----------------+

але перша етикетка не завжди заповнюється вмістом, іноді для нього не існує просто вмісту. Що я хотів би зробити, це автоматично показати мітку 2, де мітка 1 буде мати вміст.

+----------------+
| +------------+ |
| + label 2    | |
| +------------+ |
|                |
|                |
|                |
|                |
+----------------+

Які обмеження мені доведеться додати, тому він автоматично працює з автозаповненням? Я розумію, що я можу просто кодувати все, але у мене є близько 30 таких міток та зображень та кнопок різних стилів і форм, які є необов'язковими, і я не хочу додавати гудретів рядків коду, коли він може працювати автоматично дуже добре теж .

Якщо це не спрацює, я буду просто використовувати WebView і робити це за допомогою HTML та CSS.

29
Таблиця допоможе, якщо вона буде просто від верху донизу, але деякі речі теж зліва направо і повинні зайняти місце інших і речі. Але ідея не погана, я визнаю.
додано Автор Jeena, джерело
Я не впевнений, що це можливо завдяки автоматичному компонуванню, але видається, що те, що ви дійсно хочете, - це таблиця.
додано Автор user557219, джерело

8 Відповіді

Це можливо завдяки автоматичному компонуванню, але не дуже добре.

Отже, якщо взяти ваш приклад, скажімо, ви маєте позначку "А", а напис "B" (або кнопку або щось інше). По-перше, починаючи з додавання верхнього обмеження до superview для A. Тоді вертикальний інтервал обмеження між A і B. Це все нормально до цих пір. Якщо б ви мали вилучити А в даний момент, то б мав би неоднозначний макет. Якщо б ви хотіли сховати його, воно все одно займе це простір, включаючи пробіл між мітками.

Далі вам потрібно додати ще одне обмеження з B до верхньої частини superview. Змініть пріоритет на це, щоб бути нижче, ніж інші (скажімо 900), а потім встановіть, що він є постійним, щоб бути стандартним (або іншим меншим значенням). Тепер, коли А віддаляється від цього спостереження, нижчий пріоритет обмежується і потягне B до вершини. Обмеження виглядають приблизно так:

Interface Builder screenshot

Ця проблема виникає, коли ви намагаєтеся це зробити за допомогою довгого списку міток.

24
додано
Це чисте рішення для представленого випадку. Для випадку з більшістю етикеток, найімовірніше, це найлегше копіювати. IB пропонує рішення в спливаючому вікні "Додати новий обмеження" редактор, коли він посилається на "найближчий сусід". Якщо це дійсно зробило саме це, він буде працювати з коробки. Я думаю, проблема полягає в тому, що вони є обов'язковими ("neigbhor") занадто рано, і ви можете побачити це в форматі файлу розкадрування.
додано Автор Chris Conover, джерело
як працює друга частина цієї роботи? Якщо ви додаєте ще одне обмеження від B до контролю, чи не завжди він спрацьовує незалежно від пріоритету?
додано Автор kevinl, джерело
Ніколи не думаю, я намагався використати цю конкретну логіку на моєму сценарії, який не вдалося працювати: у мене є кнопка A, B, C. Я видаляю B, і я хочу, щоб C зайняв місце Б.
додано Автор kevinl, джерело

Згортаючий підклас 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
10
додано
Ну ... чудове питання. Загалом, тематика автомата не була добре обговорена на SO. Я також думаю, що багато людей просто підключили розетку до обмеження і били висоту. Підкатегорії також трохи дратує.
додано Автор Cameron Lowell Palmer, джерело
Поля, як у H: | - (8) - [view1] - (12) - [hidden1] - (8) - |? Так ... Ви маєте рацію, але це однакова проблема, коли ви мали кадри. Ви все одно повинні переконатися, що відстань між представленнями обробляється, але це окреме питання!
додано Автор Cameron Lowell Palmer, джерело
Проблема з цим підходом полягає в тому, що вона не враховує запасів, які ви, звичайно, маєте за своїми думками.
додано Автор tcurdt, джерело
Це таке акуратне рішення, яке працює і в какао, просто перемкніть UIViewNoIntrinsicMetric на NSViewNoInstrinsicMetric , updateConstraintsIfNeeded до updateConstraintsForSubtreeIfNeeded і layoutIfNeeded до layoutSubtreeIfNeeded , і це працює як чарівність. Дякую!
додано Автор bithavoc, джерело
Це, здається, дійсно простий, елегантний спосіб піти. Мені цікаво, чому його не проголосували вище, якщо він насправді працює?
додано Автор software evolved, джерело

Ця категорія призводить до стирання обмежених поглядів на автоматичний макет.

https://github.com/depth42/AutolayoutExtensions

Я просто додав його до проекту, і це чудово працює.

4
додано
Я намагаюся використати це в моєму проекті, але новий відкритий продукт PWHidingMasterView не відображається в Xcode для перегляду мого проекту. Коли я відкриваю проект зразка, там знаходиться розетка. Я не можу зрозуміти, що відрізняється від мого проекту та зразка. Будь-які поради?
додано Автор Ricardo Sanchez-Saez, джерело

Я не думаю, що ти міг це зробити так. Якщо ви зробили макет для етикетки 2, він повинен базуватися на обмеженні відстані від етикетки 1, навіть якщо ви автоматично згортали етикетку 1 до нульової висоти, якщо вона не містить вмісту, етикетка 2 все ще буде такою відстань, тобто в:

+----------------+
| +------------+ |
| + label 1    | |
| +------------+ |
|        ^       |
|        ^       !
| +------------+ |
| | label 2    | |
| +------------+ |
+----------------+

Де ^ - обмеження автозагальної відстані. Якщо мітка 1 знає, як стати нульовою висотою, коли рядок порожній, ви все одно отримаєте:

+----------------+
| +------------+ |
|        ^       |
|        ^       !
| +------------+ |
| | label 2    | |
| +------------+ |
+----------------+

Maybe it is possible by creating your NSLayoutConstraint manually. You could make the second attribute be the height of label 1, make the constant zero, and then carefully work out what the multiplier would be to make the distance be what you want based on a multiple of the non-zero label height.

Але, зробивши все це, ви зараз закодовали підклас NSLabel, який автоматично розмірів, створив об'єкт обмеження вручну замість візуальної мови та нахилив NSLayoutConstraint за його бажанням.

Я думаю, вам краще просто змінити рамку етикетки 2, якщо рядок мітки 1 є порожнім!

2
додано
без встановлення константи до 0. це будь-який прямий варіант, доступний у конструкторі інтерфейсу?
додано Автор user2223516, джерело

Ось приклад того, як я виконував цю програму, а не користувався інтерфейсом 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.

Спочатку я реалізував це на основі відповіді на цю версію переповнення стек і змінив речі, щоб відповідати моїм власним потребам.

1
додано

Я також знайшов пристойний спосіб це зробити. Це схоже на Девід . Ось як працює код. Я створив наглядовий підхід і всі підвільні зображення, навіть ті, які не завжди можуть бути показані. Я додав багато обмежень, таких як V: | - [_btn] до нагляду. Як ви можете бачити в кінці цих обмежень немає посилання на дно на superview. Тоді я створив два масиви обмежень для обох станів зору, для мене різниця - трикутник розкриття інформації "Додаткові параметри". Тоді, коли трикутник натискається в залежності від його стану, я додаю і видаляю відповідні обмеження та підзвіт. Наприклад, щоб додати:

[self.backgroundView removeConstraints:self.lessOptionsConstraints];
[self.backgroundView addSubview:self.nameField];
[self.backgroundView addConstraints:self.moreOptionsConstraints];

Обмеження, які я зняв, прив'язали кнопку до нижньої панелі перегляду, як-от V: [_ btn] - | . Додані вами обмеження виглядають як V: [_ btn] - [_ nameField] - | , як ви можете побачити це обмеження, поміщає новий вигляд між оригінальним представленням над ним і нижньою панеллю перегляду, яка поширює висота спостереження.

0
додано

Вирішене це питання програмним шляхом. У мене кілька кнопок поспіль, і я можу вирішити приховати його в будь-який час.

enter image description here

Використовується Картографія , щоб замінити їх кожен раз, коли 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
}
0
додано

Я знайшов інший спосіб це зробити. Ця методологія може застосовуватися будь-де, не має масштабних проблем; і обробляє поля. І для цього вам не потрібні треті сторонні речі.

По-перше, не використовуйте цей макет:

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 на ньому

І

  • Disabling the Top, Bottom, Leading І Trailing constraints. So seek them out, than set Active to NO on them. This will cause the Margin-Container to have a Frame Size of (0,0); because it does have subview(s); but there aren't any (active) layout constraints which anchors those subviews to it.

Maybe a bit complex, but you only have to develop it once. All the logic can be put into a separate place, І be reused every time you need to hide smg.

Ось код C# Xamarin про те, як шукати ті обмеження, які закріплюють підгляд (-и) до внутрішніх країв вигляду Margin-Container :

public List 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 &= ViewІSubviews.Contains(c.FirstItem);//ViewІSubviews: The View І View.Subviews
            predicate &= ViewІSubviews.Contains(c.SecondItem);
            return predicate;
        }).ToList();
    }
}
0
додано