Подтвердить что ты не робот

Вложенные UIStackViews Broken Constraints

У меня сложная иерархия представлений, построенная в Interface Builder, с вложенными UIStackViews. Я получаю уведомления об "неудовлетворительных ограничениях" каждый раз, когда я скрываю некоторые из своих внутренних стеков. Я отслеживал это:

(
    "<NSLayoutConstraint:0x1396632d0 'UISV-canvas-connection' UIStackView:0x1392c5020.top == UILabel:0x13960cd30'Also available on iBooks'.top>",
    "<NSLayoutConstraint:0x139663470 'UISV-canvas-connection' V:[UIButton:0x139554f80]-(0)-|   (Names: '|':UIStackView:0x1392c5020 )>",
    "<NSLayoutConstraint:0x139552350 'UISV-hiding' V:[UIStackView:0x1392c5020(0)]>",
    "<NSLayoutConstraint:0x139663890 'UISV-spacing' V:[UILabel:0x13960cd30'Also available on iBooks']-(8)-[UIButton:0x139554f80]>"
)

В частности, ограничение UISV-spacing: при скрытии UIStackView его высокое ограничение получает константу 0, но похоже, что она сталкивается с внутренним ограничением интервала стеков: она требует 8 точек между моей меткой и кнопкой, что непримиримо с скрывая ограничение, и поэтому сбой ограничений.

Есть ли способ обойти это? Я попытался рекурсивно скрыть все внутренние StackViews скрытого представления стека, но это приводит к странной анимации, когда контент выплывает из экрана и вызывает серьезные потери FPS для загрузки, но при этом не устраняет проблему.

4b9b3361

Ответ 1

В идеале мы могли бы просто установить приоритет ограничения UISV-spacing на более низкое значение, но, похоже, нет никакого способа сделать это.:)

У меня есть успех, устанавливающий свойство spacing вложенных представлений стека до 0 перед тем, как скрыть, и восстановление до правильного значения после повторного его отображения.

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

Мой проект имеет только один уровень вложенности, поэтому я не уверен, что это приведет к проблемам с FPS. Пока вы не изменяете изменения в интервалах, я не думаю, что это создало бы слишком много хитов.

Ответ 2

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

Существуют 3 решения этой проблемы:

  • Измените интервал на 0, но тогда вам нужно будет запомнить предыдущее значение расстояния.
  • Вызовите innerStackView.removeFromSuperview(), но тогда вам нужно будет запомнить, куда вставить представление стека.
  • Оберните представление стека в UIView с хотя бы одним ограничением 999. Например. top @1000, ведущий @1000, конечный @1000, внизу @999.

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

Ответ 3

Я столкнулся с аналогичной проблемой с UISV-скрытием. Для меня решение заключалось в том, чтобы уменьшить приоритеты моих собственных ограничений от Required (1000) до чего-то меньшего. Когда добавляются ограничения скрытия UISV, они принимают приоритет, и ограничения больше не конфликтуют.

Ответ 4

Итак, у вас есть это:

сломанная анимация

И проблема в том, что когда вы сначала сворачиваете внутренний стек, вы получаете ошибки автоматического макета:

2017-07-02 15:40:02.377297-0500 nestedStackViews[17331:1727436] [LayoutConstraints] Unable to simultaneously satisfy constraints.
    Probably at least one of the constraints in the following list is one you don't want. 
    Try this: 
        (1) look at each constraint and try to figure out which you don't expect; 
        (2) find the code that added the unwanted constraint or constraints and fix it. 
(
    "<NSLayoutConstraint:0x62800008ce90 'UISV-canvas-connection' UIStackView:0x7fa57a70fce0.top == UILabel:0x7fa57a70ffb0'Top Label of Inner Stack'.top   (active)>",
    "<NSLayoutConstraint:0x62800008cf30 'UISV-canvas-connection' V:[UILabel:0x7fa57d30def0'Bottom Label of Inner Sta...']-(0)-|   (active, names: '|':UIStackView:0x7fa57a70fce0 )>",
    "<NSLayoutConstraint:0x62000008bc70 'UISV-hiding' UIStackView:0x7fa57a70fce0.height == 0   (active)>",
    "<NSLayoutConstraint:0x62800008cf80 'UISV-spacing' V:[UILabel:0x7fa57a70ffb0'Top Label of Inner Stack']-(8)-[UILabel:0x7fa57d30def0'Bottom Label of Inner Sta...']   (active)>"
)

Will attempt to recover by breaking constraint 
<NSLayoutConstraint:0x62800008cf80 'UISV-spacing' V:[UILabel:0x7fa57a70ffb0'Top Label of Inner Stack']-(8)-[UILabel:0x7fa57d30def0'Bottom Label of Inner Sta...']   (active)>

Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger.
The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKit/UIView.h> may also be helpful.

Проблема, как вы заметили, заключается в том, что внешний вид стека применяет ограничение height = 0 к представлению внутреннего стека. Это конфликтует с 8-кратным ограничением заполнения, применяемым представлением внутреннего стека между его собственными подзонами. Оба ограничения не могут выполняться одновременно.

Внешний вид стека использует это ограничение height = 0, я считаю, потому что он выглядит лучше, когда он анимирован, чем просто позволяет скрывать внутренний вид, не сокращаясь сначала.

Там простое исправление: оберните представление внутреннего стека в простой UIView и скройте эту оболочку. Я продемонстрирую.

Здесь схема сцены для сломанной версии выше:

сломанная схема

Чтобы устранить проблему, выберите представление внутреннего стека. В строке меню выберите "Редактор" > "Вставить" > "Вид":

embed in view

Интерфейс Builder создал ограничение ширины в представлении оболочки, когда я это сделал, поэтому удалите ограничение ширины:

delete width constraint

Затем создайте ограничения между всеми четырьмя краями обертки и представлением внутреннего стека:

создать ограничения

В этот момент макет фактически корректен во время выполнения, но Interface Builder рисует его неправильно. Вы можете исправить это, установив приоритеты вертикального обнимания детей внутреннего стека выше. Я установил их на 800:

обнимающие приоритеты

На данный момент мы на самом деле не зафиксировали неудовлетворительную проблему ограничения. Для этого найдите нижнее ограничение, которое вы только что создали, и установите его приоритет менее необходимым. Позвольте изменить его на 800:

изменить приоритет нижнего ограничения

Наконец, вы предположительно имели выход в своем контроллере вида, подключенного к представлению внутреннего стека, потому что вы меняли его свойство hidden. Измените эту розетку для подключения к представлению обертки вместо представления внутреннего стека. Если тип вашего выхода UIStackView, вам нужно изменить его на UIView. Мой был уже типа UIView, поэтому я только что подключил его в раскадровке:

change outlet

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

Вы можете найти мой тестовый проект в этом репозитории github.

Ответ 5

Здесь реализована реализация предложения Senseful № 3, написанного как класс Swift 3 с использованием ограничений SnapKit. Я также попытался переопределить свойства, но никогда не работал без предупреждений, поэтому я буду придерживаться оболочки UIStackView:

class NestableStackView: UIView {
    private var actualStackView = UIStackView()

    override init(frame: CGRect) {
        super.init(frame: frame);
        addSubview(actualStackView);
        actualStackView.snp.makeConstraints { (make) in
            // Lower edges priority to allow hiding when spacing > 0
            make.edges.equalToSuperview().priority(999);
        }
    }

    convenience init() {
        self.init(frame: CGRect.zero);
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    func addArrangedSubview(_ view: UIView) {
        actualStackView.addArrangedSubview(view);
    }

    func removeArrangedSubview(_ view: UIView) {
        actualStackView.removeArrangedSubview(view);
    }

    var axis: UILayoutConstraintAxis {
        get {
            return actualStackView.axis;
        }
        set {
            actualStackView.axis = newValue;
        }
    }

    open var distribution: UIStackViewDistribution {
        get {
            return actualStackView.distribution;
        }
        set {
            actualStackView.distribution = newValue;
        }
    }

    var alignment: UIStackViewAlignment {
        get {
            return actualStackView.alignment;
        }
        set {
            actualStackView.alignment = newValue;
        }
    }

    var spacing: CGFloat {
        get {
            return actualStackView.spacing;
        }
        set {
            actualStackView.spacing = newValue;
        }
    }
}

Ответ 6

Другой подход

Попробуйте избежать вложенных UIStackViews. Я люблю их и строю почти все с ними. Но поскольку я понял, что они тайно добавляют ограничения, я стараюсь использовать их только на самом высоком уровне и, если это возможно, не вложенные. Таким образом, я могу указать второй наивысший приоритет .defaultHigh на ограничение расстояния, которое разрешает мои предупреждения.

Этот приоритет достаточно для предотвращения большинства проблем с макетами.

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