Автоматична розкладка із прихованими UIView?


164

Я відчуваю, що це досить поширена парадигма, яку потрібно показувати / ховати UIViews, найчастіше UILabels, залежно від логіки бізнесу. Моє запитання полягає в тому, що найкращий спосіб використовувати AutoLayout для відповіді на приховані види, як якщо б їх кадр був 0x0. Ось приклад динамічного списку 1-3 функцій.

Динамічний список функцій

Зараз у мене є верхній пробіл у розмірі 10 пікселів від кнопки до останньої мітки, який, очевидно, не буде ковзати вгору, коли мітка прихована. На даний момент я створив розетку для цього обмеження і змінив константу залежно від того, скільки міток я показую. Це, очевидно, трохи хакі, оскільки я використовую негативні постійні значення, щоб натиснути кнопку вгору над прихованими кадрами. Це також погано, оскільки його не обмежують фактичні елементи компонування, просто підступні статичні обчислення, засновані на відомих висотах / прокладці інших елементів, і, очевидно, борються з тим, для чого створено AutoLayout.

Я, очевидно, міг би просто створити нові обмеження залежно від моїх динамічних міток, але це багато керування мікроменеджментом та багато багатослівностей для спроб просто згортати деякий пробіл. Чи є кращі підходи? Зміна розміру кадру 0,0 та дозволяючи AutoLayout робити свою справу без маніпуляцій із обмеженнями? Повністю видалити представлення даних?

Чесно кажучи, що для зміни константи з контексту прихованого перегляду потрібен єдиний рядок коду з простим обчисленням. Відтворення нових обмежень constraintWithItem:attribute:relatedBy:toItem:attribute:multiplier:constant:здається таким важким.

Редагувати Лютий 2018 : Дивіться відповідь Бена з UIStackViews


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

Відповіді:


82

UIStackViewце, мабуть, шлях для iOS 9+. Він не тільки обробляє прихований вигляд, але також буде видаляти додаткові інтервали та поля, якщо їх правильно встановити.


8
Будьте уважні при використанні UIStackView в UITableViewCell. Для мене, як тільки перегляд дуже ускладнився, прокрутка почала ставати холодною, де вона буде заїкатися кожного разу, коли комірка прокручується / виходить. Під обкладинками StackView додає / видаляє обмеження, і це не обов'язково буде досить ефективним для плавного прокручування.
Кенто

7
Було б добре навести невеликий приклад того, як перегляд стека обробляє це.
програв переклад

@Kento це все ще проблема на iOS 10?
Crashalot

3
Через п’ять років ... я повинен оновити та встановити це як прийняту відповідь? Зараз він доступний протягом трьох циклів випуску.
Райан Романчук

1
@RyanRomanchuk Ви повинні, це, безумовно, найкраща відповідь зараз. Дякую Бен!
Дункан Лука

234

Мої особисті переваги щодо показу / приховування поглядів - створити IBOutlet з відповідним обмеженням по ширині або висоті.

Потім я оновлюю constantзначення, щоб 0його приховати, або те, що має бути показано.

Великою перевагою цієї методики є те, що відносні обмеження будуть зберігатися. Наприклад, скажімо, у вас є вид A і вид B з горизонтальним зазором x . Коли для перегляду встановлено ширину constant, 0.fвигляд B переміститься вліво, щоб заповнити цей простір.

Не потрібно додавати або видаляти обмеження, що є важкою операцією. Просто оновлення обмежень constantдозволить зробити це.


1
точно. Поки - у цьому прикладі ви розміщуєте вид B праворуч від виду A, використовуючи горизонтальний зазор. Я постійно використовую цю техніку, і вона працює дуже добре
Макс МакЛеод,

9
@MaxMacLeod Просто для того, щоб переконатися: якщо розрив між двома видами є x, для того, щоб погляд B почався з позиції view A, ми також повинні змінити константу обмеження зазору на 0 також, правда?
kernix

1
@kernix так, якщо потрібно буде обмежити горизонтальну щілину між двома поглядами. константа 0 звичайно означає відсутність розриву. Таким чином, приховуючи подання A, встановивши його ширину на 0, переміститься подання B вліво, щоб воно було врівень з контейнером. До речі, дякую за голоси!
Макс Маклеод

2
@MaxMacLeod Дякуємо за вашу відповідь. Я спробував це зробити з UIView, який містить інші погляди, але підпогляди цього виду не приховані, коли я встановив обмеження по висоті до 0. Чи повинно це рішення працювати з видами, що містять підпрегляди? Дякую!
shaunlim

6
Буде також вдячний за рішення, яке працює для UIView, що містить інші погляди ...
Марк Гібо

96

Рішення використання константи 0при прихованому та іншої константи, якщо ви знову покажете це, є функціональним, але незадовільним, якщо ваш вміст має гнучкий розмір. Вам потрібно буде виміряти свій гнучкий вміст і встановити постійну спину. Це відчуває себе неправильно та має проблеми, якщо вміст змінює розмір через події сервера чи інтерфейсу користувача.

У мене є краще рішення.

Ідея полягає у встановленні правила висоти 0, яке має високий пріоритет, коли ми ховаємо елемент, щоб він не займав місця для автоматичного вимикання.

Ось як це зробити:

1. встановіть ширину (або висоту) 0 у програмі побудови інтерфейсу з низьким пріоритетом.

правило ширини 0

Інтерфейс Builder не кричить про конфлікти, оскільки пріоритет низький. Перевірте поведінку на висоті, встановивши пріоритет на 999 тимчасово (1000 заборонено програмно мутувати, тому ми не будемо його використовувати). Інтерфейс-конструктор, ймовірно, тепер буде кричати про конфліктні обмеження. Ви можете їх виправити, встановивши пріоритети на пов'язаних об'єктах до 900 або близько того.

2. Додайте розетку, щоб ви могли змінити пріоритет обмеження ширини в коді:

підключення до розетки

3. Відрегулюйте пріоритет, коли ви ховаєте свій елемент:

cell.alertTimingView.hidden    = place.closingSoon != true
cell.alertTimingWidth.priority = place.closingSoon == true ? 250 : 999

@SimplGy, будь ласка, скажіть мені, що це за місце.closingSoon?
Ніхарика

Це не є частиною загального рішення, @Niharika, це якраз те, що в моєму випадку було діловим правилом (покажіть думку, якщо ресторан не закриється скоро).
SimplGy

11

У цьому випадку я зіставляю висоту мітки Author відповідному IBOutlet:

@property (retain, nonatomic) IBOutlet NSLayoutConstraint* authorLabelHeight;

і коли я встановив висоту обмеження на 0,0f, ми зберігаємо "підкладку", оскільки висота кнопки "Відтворення" дозволяє це.

cell.authorLabelHeight.constant = 0; //Hide 
cell.authorLabelHeight.constant = 44; //Show

введіть тут опис зображення


9

Тут є маса рішень, але мій звичайний підхід знову інший :)

Створіть два набори обмежень, подібних до відповіді Хорхе Арімані та TMin:

введіть тут опис зображення

Усі три позначені обмеження мають однакове значення для постійних. Для обмежень, позначених A1 і A2, встановлено пріоритет 500, тоді як обмеження, позначене B, має пріоритет на 250 (або UILayoutProperty.defaultLowв коді).

Підключіть обмеження B до IBOutlet. Потім, коли ви ховаєте елемент, вам просто потрібно встановити пріоритет обмеження на високий (750):

constraintB.priority = .defaultHigh

Але коли елемент видно, встановіть пріоритет назад на низький (250):

constraintB.priority = .defaultLow

Перевагою цього підходу (щоправда, незначною) перед просто зміною isActiveобмеження В є те, що ви все ще маєте робоче обмеження, якщо перехідний елемент видаляється з виду іншими способами.


Це має переваги у відсутності дублюючих значень на аркуші розгортки та такому коді, як висота / ширина елементів. Дуже елегантний.
Робін Догерті

Дякую!! Це допомогло мені
Парт Барот

Чудовий підхід, дякую
Василь

5

Підклас перегляду та замінення func intrinsicContentSize() -> CGSize. Просто поверніться, CGSizeZeroякщо вигляд прихований.


2
Це чудово. Деякі елементи інтерфейсу потрібні для запуску invalidateIntrinsicContentSize. Це можна зробити в переоціненому вигляді setHidden.
DrMickeyLauer

5

Я щойно з’ясував, що щоб отримати UILabel, щоб не зайняти місце, потрібно його сховати І встановити його текст у порожній рядок. (iOS 9)

Знання цього факту / помилки може допомогти деяким людям спростити їх макети, можливо, навіть питання оригіналу, тому я зрозумів, що опублікую це.


1
Я не думаю, що це потрібно приховувати. Встановлення тексту в порожній рядок повинно бути достатньо.
Роб VS

4

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

Оскільки підключення обмежень IBOutletsі встановлення їх констант 0відчувалося похмурим (і викликало NSLayoutConstraintпопередження, коли у вашої точки зору були підзагляди), я вирішив створити розширення, яке надає простий, позитивний підхід до приховування / показу, UIViewщо має обмеження щодо автоматичного макета.

Це просто приховує погляд і знімає зовнішні обмеження. Коли ви знову покажете погляд, він додає обмеження назад. Єдине застереження полягає в тому, що вам потрібно буде вказати гнучкі обмеження на відмову від оточуючих поглядів.

Редагувати Ця відповідь націлена на iOS 8.4 та нижче. В iOS 9 просто використовуйте UIStackViewпідхід.


1
Це теж мій переважний підхід. Це суттєве вдосконалення щодо інших відповідей тут, оскільки це не просто скорочує погляд на нульовий розмір. Підхід сквош повинен мати стукати в питаннях, окрім як тривіальних виплат. Прикладом цього є великий проміжок, де прихований предмет знаходиться в цій іншій відповіді . І ви можете закінчитися порушеннями обмежень, як згадувалося.
Бенджон

@DanielSchlaug Дякую, що вказали на це. Я оновив ліцензію на MIT. Однак мені потрібно вимагати розумової п'ятирічки кожного разу, коли хтось реалізує це рішення.
Альберт Борі

4

Найкраща практика - як тільки все має правильні обмеження у компонуванні, додайте висоту або обмеження, залежно від того, як ви хочете, щоб оточуючі види перенесли та з'єднали обмеження у IBOutletвластивість.

Переконайтеся, що ваші властивості strong

у коді yo просто потрібно встановити константу на 0 і активувати її, щоб приховати вміст або вимкнути його, щоб показати вміст. Це краще, ніж псуватися з постійною величиною, економлячи-відновлюючи її. Не забудьте зателефонувати layoutIfNeededпісля цього.

Якщо вміст, який потрібно приховати, згруповано, найкращим методом є все це переглядати та додавати обмеження до цього подання

@property (strong, nonatomic) IBOutlet UIView *myContainer;
@property (strong, nonatomic) IBOutlet NSLayoutConstraint *myContainerHeight; //should be strong!!

-(void) showContainer
{
    self.myContainerHeight.active = NO;
    self.myContainer.hidden = NO;
    [self.view layoutIfNeeded];
}
-(void) hideContainer
{
    self.myContainerHeight.active = YES;
    self.myContainerHeight.constant = 0.0f;
    self.myContainer.hidden = YES;
    [self.view layoutIfNeeded];
}

Після встановлення ви можете протестувати його в IntefaceBuilder, встановивши обмеження на 0, а потім повернутися до початкового значення. Не забувайте перевіряти інші пріоритети обмежень, тому, коли їх приховано, взагалі не виникає конфліктів. інший спосіб перевірити це - встановити його на 0 і встановити пріоритет на 0, але, ви не повинні забувати знову відновити його до найвищого пріоритету.


1
Це тому, що обмеження та перегляд не завжди можуть бути збережені ієрархією перегляду, тому, якщо ви деактивувати обмеження і приховати перегляд, ви все одно зберігаєте їх, щоб відобразити їх знову, незалежно від того, яку вашу ієрархію перегляду вирішили зберегти чи ні.
Хорхе Арімані

3

Я будую категорію, щоб легко оновлювати обмеження:

[myView1 hideByHeight:YES];

Відповідь тут: Сховати автоматичний розклад UIView: як отримати існуючий NSLayoutConstraint для оновлення цього

введіть тут опис зображення


8
На жаль, ви отримуєте подвійний інтервал там, де саме вказує жовта стрілка.
Золтан

Як і в інших рішеннях вище, я думаю. Зовсім не ідеально.
Benjohn

2

Мій кращий метод дуже схожий на той, який запропонував Хорхе Арімані.

Я вважаю за краще створювати кілька обмежень. Спочатку створіть свої обмеження для того, коли видно другу мітку. Створіть розетку для обмеження між кнопкою та 2-ю міткою (якщо ви використовуєте objc, переконайтеся, що її міцність). Це обмеження визначає висоту між кнопкою та другою міткою, коли її видно.

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

Нарешті, коли ви ховаєте другу мітку, перемкніть .isActiveвластивість цих обмежень і зателефонуйтеsetNeedsDisplay()

І це все, ніяких магічних чисел, ніякої математики, якщо у вас є кілька обмежень для включення та вимкнення, ви навіть можете використовувати колекції розетки, щоб їх організувати держава. (AKA зберігає всі приховані обмеження в одному OutletCollection, а не приховані - в іншому і просто повторює кожну колекцію, змінюючи їх статус .isActive).

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

Я створив простий приклад, сподіваюся, що це корисно ...

import UIKit

class ViewController: UIViewController {

    @IBOutlet var ToBeHiddenLabel: UILabel!


    @IBOutlet var hiddenConstraint: NSLayoutConstraint!
    @IBOutlet var notHiddenConstraint: NSLayoutConstraint!


    @IBAction func HideMiddleButton(_ sender: Any) {

        ToBeHiddenLabel.isHidden = !ToBeHiddenLabel.isHidden
        notHiddenConstraint.isActive = !notHiddenConstraint.isActive
        hiddenConstraint.isActive = !hiddenConstraint.isActive

        self.view.setNeedsDisplay()
    }
}

введіть тут опис зображення


0

Я також запропоную своє рішення, пропоную різноманітність.) Я думаю, що створення розетки для ширини / висоти кожного елемента плюс для інтервалів просто смішно і підірває код, можливі помилки та кількість ускладнень.

Мій метод видаляє всі представлення даних (у моєму випадку екземпляри UIImageView), вибирає, які з них потрібно додати назад, а в циклі додає назад кожен і створює нові обмеження. Це насправді дуже просто, будь ласка, слідкуйте за цим. Ось мій швидкий і брудний код для цього:

// remove all views
[self.twitterImageView removeFromSuperview];
[self.localQuestionImageView removeFromSuperview];

// self.recipients always has to be present
NSMutableArray *items;
items = [@[self.recipients] mutableCopy];

// optionally add the twitter image
if (self.question.sharedOnTwitter.boolValue) {
    [items addObject:self.twitterImageView];
}

// optionally add the location image
if (self.question.isLocal) {
    [items addObject:self.localQuestionImageView];
}

UIView *previousItem;
UIView *currentItem;

previousItem = items[0];
[self.contentView addSubview:previousItem];

// now loop through, add the items and the constraints
for (int i = 1; i < items.count; i++) {
    previousItem = items[i - 1];
    currentItem = items[i];

    [self.contentView addSubview:currentItem];

    [currentItem mas_remakeConstraints:^(MASConstraintMaker *make) {
        make.centerY.equalTo(previousItem.mas_centerY);
        make.right.equalTo(previousItem.mas_left).offset(-5);
    }];
}


// here I just connect the left-most UILabel to the last UIView in the list, whichever that was
previousItem = items.lastObject;

[self.userName mas_remakeConstraints:^(MASConstraintMaker *make) {
    make.right.equalTo(previousItem.mas_left);
    make.leading.equalTo(self.text.mas_leading);
    make.centerY.equalTo(self.attachmentIndicator.mas_centerY);;
}];

У мене виходить чистий, послідовний макет та інтервали. Мій код використовує масонство, настійно рекомендую його: https://github.com/SnapKit/Masonry


0

Спробуйте BoxView , це робить динамічний макет лаконічним і читабельним.
У вашому випадку це:

    boxView.optItems = [
        firstLabel.boxed.useIf(isFirstLabelShown),
        secondLabel.boxed.useIf(isSecondLabelShown),
        button.boxed
    ]
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.