UIStackView Скрыть просмотр анимации

83

В iOS 11 поведение анимации скрытия внутри a UIStackViewизменилось, но я нигде не смог найти это задокументировано.

iOS 10

iOS 10 анимация

iOS 11

iOS 11 анимация

Код в обоих случаях таков:

UIView.animate(withDuration: DiscoverHeaderView.animationDuration,
                       delay: 0.0,
                       usingSpringWithDamping: 0.9,
                       initialSpringVelocity: 1,
                       options: [],
                       animations: {
                            clear.isHidden = hideClear
                            useMyLocation.isHidden = hideLocation
                        },
                       completion: nil)

Как восстановить прежнее поведение на iOS 11?

Бесконечность Джеймс
источник

Ответы:

133

Была такая же проблема. Исправление заключается в добавлении stackView.layoutIfNeeded()внутри блока анимации. Где stackViewконтейнер с предметами, которые вы хотите спрятать?

UIView.animate(withDuration: DiscoverHeaderView.animationDuration,
                   delay: 0.0,
                   usingSpringWithDamping: 0.9,
                   initialSpringVelocity: 1,
                   options: [],
                   animations: {
                        clear.isHidden = hideClear
                        useMyLocation.isHidden = hideLocation
                        stackView.layoutIfNeeded()
                    },
                   completion: nil)

Не уверен, почему это внезапно стало проблемой в iOS 11, но, честно говоря, это всегда был рекомендуемый подход.

Спрингхэм
источник
1
Ты герой: D
Бесконечность Джеймс
5
Подходящее имя также 'Springham' 😆
Infinity James
4
В прошивкой <= 10 была ошибка , в которой установка hiddenсвойства UIStackViewsubviewв блоке анимации была игнорируются в некоторых случаях, так что лучший способ , чтобы изменить его за его пределами, прямо перед анимацией.
Юлиан
2
view.layoutIfNeeded()Возможно, я ошибаюсь, но это не звучит из документов, как будто бы обновили положение других представлений в StackView, чего мы и хотим. developer.apple.com/documentation/uikit/uiview/…
Спрингхэм
6
view.layoutIfNeeded () в порядке, однако вызов view.isHidden = true, если представление уже скрыто (или наоборот), нарушает работу. Поэтому убедитесь, что представление уже не является скрытым состоянием, которое вы хотите изменить. if (view.isHidden == true) {view.isHidden = false}
glemoulant
5

Расширение Swift 4:

// MARK: - Show hide animations in StackViews

extension UIView {

func hideAnimated(in stackView: UIStackView) {
    if !self.isHidden {
        UIView.animate(
            withDuration: 0.35,
            delay: 0,
            usingSpringWithDamping: 0.9,
            initialSpringVelocity: 1,
            options: [],
            animations: {
                self.isHidden = true
                stackView.layoutIfNeeded()
            },
            completion: nil
        )
    }
}

func showAnimated(in stackView: UIStackView) {
    if self.isHidden {
        UIView.animate(
            withDuration: 0.35,
            delay: 0,
            usingSpringWithDamping: 0.9,
            initialSpringVelocity: 1,
            options: [],
            animations: {
                self.isHidden = false
                stackView.layoutIfNeeded()
            },
            completion: nil
        )
    }
}
}
Ergunkocak
источник
5
Для меня исправление заключалось в том, чтобы проверять, self.isHiddenа не устанавливать значение, если оно уже такое же.
Richy
1
это может быть одна функция с именем toggleAnimated (в ..., show: Bool). так как меняется только одна строка :) плюс это не сработало для меня: s
Жан Раймон Дахер
Да, две функции были бы синтаксическим сахаром после создания одной функции
ergunkocak
4

Это уже упоминалось в комментариях к принятому ответу, но это была моя проблема, и ее нет ни в одном из ответов здесь, поэтому:

Убедитесь в том , чтобы никогда не установить isHidden = trueна вид , что уже скрыты. Это испортит представление стека.

джимпик
источник
Это была моя проблема, и мне не нужно было звонить, layoutIfNeededпоэтому мне интересно, должен ли это быть правильный ответ.
B Roy Dawson
3

Я хочу поделиться этой функцией, которая хороша для скрытия и отображения множества представлений UIStackView, потому что весь код, который я использовал раньше, не работал гладко, потому что нужно удалить анимацию из некоторых слоев:

extension UIStackView {
    public func make(viewsHidden: [UIView], viewsVisible: [UIView], animated: Bool) {
        let viewsHidden = viewsHidden.filter({ $0.superview === self })
        let viewsVisible = viewsVisible.filter({ $0.superview === self })

        let blockToSetVisibility: ([UIView], _ hidden: Bool) -> Void = { views, hidden in
            views.forEach({ $0.isHidden = hidden })
        }

        // need for smooth animation
        let blockToSetAlphaForSubviewsOf: ([UIView], _ alpha: CGFloat) -> Void = { views, alpha in
            views.forEach({ view in
                view.subviews.forEach({ $0.alpha = alpha })
            })
        }

        if !animated {
            blockToSetVisibility(viewsHidden, true)
            blockToSetVisibility(viewsVisible, false)
            blockToSetAlphaForSubviewsOf(viewsHidden, 1)
            blockToSetAlphaForSubviewsOf(viewsVisible, 1)
        } else {
            // update hidden values of all views
            // without that animation doesn't go
            let allViews = viewsHidden + viewsVisible
            self.layer.removeAllAnimations()
            allViews.forEach { view in
                let oldHiddenValue = view.isHidden
                view.layer.removeAllAnimations()
                view.layer.isHidden = oldHiddenValue
            }

            UIView.animate(withDuration: 0.3,
                           delay: 0.0,
                           usingSpringWithDamping: 0.9,
                           initialSpringVelocity: 1,
                           options: [],
                           animations: {

                            blockToSetAlphaForSubviewsOf(viewsVisible, 1)
                            blockToSetAlphaForSubviewsOf(viewsHidden, 0)

                            blockToSetVisibility(viewsHidden, true)
                            blockToSetVisibility(viewsVisible, false)
                            self.layoutIfNeeded()
            },
                           completion: nil)
        }
    }
}
Пол Т.
источник
Это также решило проблему не исчезновения / исчезновения представлений. Красивый!
Петр Фиала
3

Расширение для скрытия / отображения отдельных элементов

Это не на 100% связано, но если вы ищете краткий способ скрыть отдельные UIViewэлементы (либо в виде стека, либо где-либо еще), вы можете использовать это простое расширение, которое я сделал:

extension UIView {
    func isHiddenAnimated(value: Bool, duration: Double = 0.2) {
        UIView.animate(withDuration: duration) { [weak self] in self?.isHidden = value }
    }
}

Я использую его для удобного скрытия / отображения элементов с анимацией в виде стека с помощью одной строчки кода. Пример:

validatableButton.isHiddenAnimated(value: false)
Алессандро Франкучи
источник
1

Надеюсь, это избавит других от нескольких часов разочарования.

Анимировать скрытие И одновременно показывать несколько подвидов UIStackView - это беспорядок.

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

    let time = 0.3

    UIView.animate(withDuration: time, animations: {

        //shows
        self.googleSignInView.isHidden = false
        self.googleSignInView.alpha = 1
        self.registerView.isHidden = false
        self.registerView.alpha = 1

        //hides
        self.usernameView.isHidden = true
        self.usernameView.alpha = 0
        self.passwordView.isHidden = true
        self.passwordView.alpha = 0

        self.stackView.layoutIfNeeded()

    }) { (finished) in

        self.googleSignInView.isHidden = false
        self.registerView.isHidden = false
        self.usernameView.isHidden = true
        self.passwordView.isHidden = true
    }
Матян
источник