101 votes

UIStackView Hide View Animation

Dans iOS 11, le comportement de l'animation de masquage au sein d'une UIStackView a changé, mais je n'ai pas pu trouver de documentation à ce sujet.

iOS 10

iOS 10 animation

iOS 11

iOS 11 animation

Le code dans les deux cas est le suivant :

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

Comment rétablir le comportement précédent sous iOS 11 ?

166voto

Springham Points 648

J'ai eu le même problème. La solution consiste à ajouter stackView.layoutIfNeeded() à l'intérieur du bloc d'animation. Où stackView est le conteneur des éléments que vous souhaitez cacher.

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)

Je ne sais pas pourquoi cela pose soudainement un problème dans iOS 11, mais pour être honnête, cela a toujours été l'approche recommandée.

1 votes

Vous êtes un héros :D

5 votes

Un nom tout aussi approprié : "Springham".

4 votes

Dans iOS <= 10, il y avait un bug dans lequel le réglage de l'option hidden d'un UIStackView 's subview dans le bloc d'animation était ignorée dans certains cas. La meilleure solution consiste donc à la modifier en dehors de celui-ci, juste avant l'animation.

27voto

jimpic Points 3213

C'est déjà mentionné dans les commentaires de la réponse acceptée, mais c'était mon problème et il n'est pas dans aucune des réponses ici donc :

Veillez à nunca set isHidden = true sur une vue qui est déjà cachée. Cela perturbera la vue de la pile.

9voto

ergunkocak Points 1593

Extension 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
        )
    }
}
}

5 votes

Pour moi, la solution consistait à vérifier self.isHidden et ne pas définir la valeur si elle est déjà la même.

1 votes

Cela pourrait facilement être une fonction appelée toggleAnimated(in... , show : Bool) . puisqu'une seule ligne change :) de plus cela n'a pas fonctionné pour moi :s

0 votes

Oui, 2 fonctions seraient du sucre syntaxique après avoir fait une seule fonction.

6voto

Paul Galavic Points 1168

Je souhaite partager cette fonction qui permet de masquer et d'afficher de nombreuses vues dans le cadre du projet UIStackView En effet, le code que j'utilisais auparavant ne fonctionnait pas correctement car il fallait supprimer l'animation de certains calques :

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)
        }
    }
}

0 votes

Cela a également résolu le problème des vues qui ne s'effacent pas. Magnifique !

4voto

Matjan Points 889

J'espère que cela évitera à d'autres quelques heures de frustration.

Animer le masquage ET l'affichage de plusieurs sous-vues UIStackView en même temps est un désordre.

Dans certains cas, les changements de .isHidden dans les blocs d'animation s'affichent correctement jusqu'à l'animation suivante, puis .isHidden est ignoré. La seule astuce fiable que j'ai trouvée pour cela est de répéter les instructions .isHidden dans la section d'achèvement du bloc d'animation.

    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
    }

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X