UIStackView Ocultar vista de animación

82

En iOS 11, el comportamiento de la animación oculta dentro de a UIStackViewha cambiado, pero no he podido encontrar esto documentado en ninguna parte.

iOS 10

Animación de iOS 10

iOS 11

Animación de iOS 11

El código en ambos es este:

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

¿Cómo restauro el comportamiento anterior en iOS 11?

Infinito james
fuente

Respuestas:

131

Solo tuve el mismo problema. La solución es agregar stackView.layoutIfNeeded()dentro del bloque de animación. ¿Dónde stackViewestá el contenedor de los artículos que desea esconder?

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)

No estoy seguro de por qué esto es de repente un problema en iOS 11, pero para ser justos, siempre ha sido el enfoque recomendado.

Un Springham
fuente
1
Eres un héroe: D
Infinity James
5
Nombre apropiado también 'Springham' 😆
Infinity James
3
En iOS <= 10 hubo un error en el que el establecimiento de la hiddenpropiedad de un UIStackView's subviewen el bloque de animación estaba siendo ignorado en algunos casos, por lo que la mejor manera es cambiar fuera de ella, justo antes de la animación.
Iulian Onofrei
2
Puede ser un malentendido de mi parte, pero no parece que los documentos view.layoutIfNeeded()actualicen la posición de otras vistas en StackView, que es lo que queremos. developer.apple.com/documentation/uikit/uiview/…
A Springham
5
view.layoutIfNeeded () está bien, sin embargo, llamar a view.isHidden = true si la vista ya está oculta (o lo contrario) rompe la cosa. Por lo tanto, asegúrese de verificar si la vista aún no es el estado oculto que desea cambiar. if (view.isHidden == true) {view.isHidden = false}
glemoulant
5

Extensión 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
fuente
5
Para mí, la solución fue verificar self.isHiddeny no establecer el valor si ya es el mismo.
richy
1
que podría ser fácilmente una función llamada toggleAnimated (en ..., mostrar: Bool). ya que solo cambia una línea :) Además, no funcionó para mí: s
Jean Raymond Daher
Sí, 2 funciones serían azúcar sintáctico después de hacer una sola función
ergunkocak
4

Ya se menciona en los comentarios de la respuesta aceptada, pero este era mi problema y no está en ninguna de las respuestas aquí, así que:

Asegúrese de no establecer nuncaisHidden = true una vista que ya esté oculta. Esto estropeará la vista de la pila.

jimpic
fuente
Este era mi problema y no necesitaba llamar, layoutIfNeededasí que me pregunto si esta debería ser la respuesta correcta.
B Roy Dawson
3

Quiero compartir esta función que es buena para ocultar y mostrar muchas vistas UIStackView, porque con todo el código que usé antes no funcionó bien porque es necesario eliminar la animación de algunas capas:

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)
        }
    }
}
Paul T.
fuente
Esto también resolvió el problema de las vistas que no aparecían / ​​desaparecían. ¡Hermosa!
Petr Fiala
3

Extensión para ocultar / mostrar elementos individuales

No está 100% relacionado, pero si está buscando una forma concisa de ocultar UIViewlos elementos individuales (ya sea en una vista de pila o en cualquier otro lugar), puede usar esta extensión simple que hice:

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

Lo uso para ocultar / mostrar convenientemente elementos con animación en una vista de pila con una sola línea de código. Ejemplo:

validatableButton.isHiddenAnimated(value: false)
Alessandro Francucci
fuente
0

Espero que esto les ahorre a otros algunas horas de frustración.

Animar la ocultación Y mostrar varias subvistas de UIStackView al mismo tiempo es un desastre.

En algunos casos, los cambios .isHidden en los bloques de animación se muestran correctamente hasta la siguiente animación, luego .isHidden se ignora. El único truco confiable que encontré para esto es repetir las instrucciones .isHidden en la sección de finalización del bloque de animación.

    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
    }
Matjan
fuente