Как использовать автоматическую компоновку для перемещения других видов при скрытии вида?

Я разработал свою собственную Cell в IB, подклассифицировал ее и подключил свои выходные данные к своему пользовательскому классу. У меня есть три представления в содержимом ячеек: UIView (cdView) и две метки (titleLabel и emailLabel). В зависимости от данных, доступных для каждой строки, иногда я хочу иметь UIView и две метки, отображаемые в моей ячейке, а иногда только две метки. То, что я пытаюсь сделать, - это установить ограничения таким образом, если я установлю свойство UIView на скрытое, или я удалю его из супервизора, эти метки будут перемещаться влево. Я попытался установить ограничение UIView на Superview (содержимое ячеек) для 10px и UILabels, ведущих ограничения для 10 px для следующего представления (UIView). Позже в моем коде

-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(IndexPath *)indexPath {
...
Record *record = [self.records objectAtIndex:indexPath.row];

if ([record.imageURL is equalToString:@""]) {
     cell.cdView.hidden = YES;
}

Я скрываю свой cell.cdView, и мне бы хотелось, чтобы метки перемещались влево, однако они остаются в одной и той же позиции в Cell. Я попытался удалить cell.cdView из супервизора, но он тоже не работал. Я приложил изображение, чтобы уточнить, о чем я говорю.

cell

Я знаю, как это сделать программно, и я не ищу этого решения. Я хочу установить ограничения в IB, и я ожидаю, что мои подзаголовки будут динамически перемещаться, если другие виды будут удалены или скрыты. Возможно ли это сделать в IB с автоматической компоновкой?

.....

Ответ 1

Возможно, но вам придется немного поработать. Есть пара концептуальных вещей, чтобы сначала уйти с пути:

  • Скрытые виды, даже если они не рисуют, все еще участвуют в Auto Layout и обычно сохраняют свои фреймы, оставляя другие связанные взгляды на своих местах.
  • При удалении представления из своего супервизора все связанные ограничения также удаляются из этой иерархии представлений.

В вашем случае это, вероятно, означает:

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

Что вам нужно сделать, это разумно over-constrain ваши ярлыки. Оставьте существующие ограничения (пространство 10pts для другого представления) в одиночку, но добавьте еще одно ограничение: сделайте левые края ваших меток на расстоянии 10pts от своего левого края супервизора с необязательным приоритетом (высокий приоритет по умолчанию, вероятно, будет работать хорошо).

Затем, когда вы хотите, чтобы они двигались влево, полностью удалите левый вид. Обязательное ограничение 10pt для левого представления исчезнет вместе с представлением, к которому оно относится, и вам останется только ограничение с высоким приоритетом, чтобы метки были на расстоянии 10pts от их супервизора. На следующем проходе макета это должно привести к тому, что они будут расширяться влево до тех пор, пока они не заполнит ширину супервизора, а за промежуток между ними.

Одно важное предостережение: если вы когда-нибудь захотите вернуться на изображение слева, не только вы должны добавить его обратно в иерархию представлений, но также вы должны восстановить все свои ограничения в в то же время. Это означает, что вам нужно установить ограничение на 10pt между представлением и его метками, когда это представление будет показано снова.

Ответ 2

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

Для просмотра, которое вы хотите скрыть, установите ограничение ширины. Ограничьте другие виды с помощью горизонтального зазора для этого вида.

Чтобы скрыть, обновите .constant ограничения ширины до 0.f. Другие виды автоматически перемещаются влево, чтобы принять положение.

См. мой другой ответ здесь для более подробной информации:

Как изменить ограничения метки во время выполнения?

Ответ 3

Для тех, кто поддерживает только iOS 8+, существует новое логическое свойство active. Это поможет динамически активировать только требуемые ограничения.

P.S. Ограничительная розетка должна быть сильной, а не слабой.

Ответ 4

UIStackView автоматически обновляет свои представления, когда свойство hidden изменяется на любом из его подзонов (iOS 9+).

UIView.animateWithDuration(1.0) { () -> Void in
   self.mySubview.hidden = !self.mySubview.hidden
}

Перейти к 11:48 в этом видео WWDC для демонстрации:

Тайны автоматического макета, часть 1

Ответ 5

В моем проекте используется пользовательский подкласс @IBDesignable UILabel (чтобы обеспечить согласованность цвета, шрифта, вставки и т.д.), и я применил что-то вроде следующего:

override func intrinsicContentSize() -> CGSize {
    if hidden {
        return CGSizeZero
    } else {
        return super.intrinsicContentSize()
    }
}

Это позволяет подклассу меток принимать участие в автоматическом макете, но не скрывать его.

Ответ 6

Для гуглеров: построив ответ Max, чтобы решить проблему заполнения, которую многие заметили, я просто увеличил высоту метки и использовал эту высоту в качестве разделителя вместо фактического заполнения. Эта идея может быть расширена для любого сценария с содержащими представлениями.

Вот простой пример:

IB Screenshot

В этом случае я сопоставляю высоту метки Author с соответствующим IBOutlet:

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

и когда я устанавливаю высоту ограничения на 0.0f, мы сохраняем "заполнение", потому что высота кнопки воспроизведения позволяет это.

Ответ 7

В результате я создал 2 xib. Один с левым видом и один без него. Я зарегистрировал их как в контроллере, а затем решил использовать для cellForRowAtIndexPath.

Они используют один и тот же класс UITableViewCell. Недостатком является то, что существует некоторое дублирование содержимого между xibs, но эти ячейки довольно простые. Поверхность заключается в том, что у меня нет кучи кода для управления удаленным просмотром, обновлениями ограничений и т.д.

В общем, это, вероятно, лучшее решение, поскольку они являются технически разными макетами и поэтому должны иметь разные xibs.

[self.table registerNib:[UINib nibWithNibName:@"TrackCell" bundle:nil] forCellReuseIdentifier:@"TrackCell"];
[self.table registerNib:[UINib nibWithNibName:@"TrackCellNoImage" bundle:nil] forCellReuseIdentifier:@"TrackCellNoImage"];

TrackCell *cell = [tableView dequeueReusableCellWithIdentifier:(appDelegate.showImages ? @"TrackCell" : @"TrackCellNoImage") forIndexPath:indexPath];

Ответ 8

установить ограничение между uiview и ярлыками как IBOutlet и установить приоритет с меньшим значением при установке hidden = YES

Ответ 9

В этом случае я сопоставляю высоту метки Author с соответствующим IBOutlet:

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

и когда я устанавливаю высоту ограничения на 0.0f, мы сохраняем "заполнение", потому что высота кнопки воспроизведения позволяет это.

cell.authorLabelHeight.constant = 0;

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

Ответ 10

Используйте два горизонтальных и вертикальных UIStackView, когда какой-либо вид в виде подзадача в стеке будет скрыт, другие подпункты стека будут перемещены, используйте Distribution → Fill Proporionally для вертикального стека с двумя UILabels и нуждаются в настройках ширины и высоты для первого UIView введите описание изображения здесь

Ответ 11

В моем случае я устанавливаю константу ограничения высоты на 0.0f, а также устанавливаю свойство hidden на YES.

Чтобы снова показать представление (с помощью subviews), я сделал противоположное: я установил постоянную высоту в ненулевое значение и установил свойство hidden в NO.

Ответ 12

Как и предлагалось no_scene, вы можете это сделать, изменив приоритет ограничения во время выполнения. Это было намного легче для меня, потому что у меня было более одного блокирующего представления, которое нужно было бы удалить.

Здесь фрагмент с использованием ReactiveCocoa:

RACSignal* isViewOneHiddenSignal = RACObserve(self.viewModel, isViewOneHidden);
RACSignal* isViewTwoHiddenSignal = RACObserve(self.viewModel, isViewTwoHidden);
RACSignal* isViewThreeHiddenSignal = RACObserve(self.viewModel, isViewThreeHidden);
RAC(self.viewOne, hidden) = isViewOneHiddenSignal;
RAC(self.viewTwo, hidden) = isViewTwoHiddenSignal;
RAC(self.viewThree, hidden) = isViewThreeHiddenSignal;

RAC(self.viewFourBottomConstraint, priority) = [[[[RACSignal
    combineLatest:@[isViewOneHiddenSignal,
                    isViewTwoHiddenSignal,
                    isViewThreeHiddenSignal]]
    and]
    distinctUntilChanged]
    map:^id(NSNumber* allAreHidden) {
        return [allAreHidden boolValue] ? @(780) : @(UILayoutPriorityDefaultHigh);
    }];

RACSignal* updateFramesSignal = [RACObserve(self.viewFourBottomConstraint, priority) distinctUntilChanged];
[updateFramesSignal
    subscribeNext:^(id x) {
        @strongify(self);
        [self.view setNeedsUpdateConstraints];
        [UIView animateWithDuration:0.3 animations:^{
            [self.view layoutIfNeeded];
        }];
    }];

Ответ 13

В случае, если это помогает кому-то, я построил вспомогательный класс для использования визуального формата. Я использую его в своем текущем приложении.

AutolayoutHelper

Это может быть немного приспособлено к моим потребностям, но вы можете найти его полезным, или вы можете его изменить и создать своего собственного помощника.

Я должен поблагодарить Тима за его ответ выше, этот ответ о UIScrollView, а также этот учебник.

Ответ 14

Здесь, как я бы повторно выравнивать мои uiviews, чтобы получить ваше решение:

  • Перетащите один UIImageView и поместите его влево.
  • Перетащите один UIView и поместите его справа от UIImageView.
  • Перетащите два UILabels внутри этого UIView, чьи начальные и конечные ограничения равны нулю.
  • Задайте ведущее ограничение UIView, содержащее 2 ярлыка, для супервизора вместо UIImagView.
  • IF UIImageView скрыт, установите для главной переменной константу ограничения 10 px для просмотра. ELSE, установите основную константу ограничения 10 px + UIImageView.width + 10 px.

Я создал правило большого пальца. Всякий раз, когда вам нужно скрыть/показать какой-либо uiview, чьи ограничения могут быть затронуты, добавьте все уязвимые/зависимые subviews внутри uiview и обновите его константу ограничения ведущего/конечного/верхнего/нижнего программных программ.

Ответ 15

Это старый вопрос, но я надеюсь, что это поможет. Начиная с Android, на этой платформе у вас есть удобный метод isVisible, чтобы скрыть его от представления, но также не учитывать фрейм, когда автозапуск рисует вид.

используя расширение и расширение "uiview", вы можете сделать аналогичную функцию в ios (не уверен, почему это не в UIKit уже) здесь реализация в swift 3:

    func isVisible(_ isVisible: Bool) {
        self.isHidden = !isVisible
        self.translatesAutoresizingMaskIntoConstraints = isVisible
        if isVisible { //if visible we remove the hight constraint 
            if let constraint = (self.constraints.filter{$0.firstAttribute == .height}.first){
                self.removeConstraint(constraint)
            }
        } else { //if not visible we add a constraint to force the view to have a hight set to 0
            let height = NSLayoutConstraint(item: self, attribute: .height, relatedBy: .equal , toItem: nil, attribute: .notAnAttribute, multiplier: 0, constant: 0)
            self.addConstraint(height)
        }
        self.layoutIfNeeded()
    }