UIStackView Ukryj animację widoku

83

W iOS 11 UIStackViewzmieniło się zachowanie animacji ukrywania w a , ale nigdzie nie mogłem znaleźć tego udokumentowanego.

iOS 10

Animacja iOS 10

iOS 11

Animacja iOS 11

Kod w obu jest następujący:

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

Jak przywrócić poprzednie zachowanie w iOS 11?

Infinity James
źródło

Odpowiedzi:

133

Po prostu miałem ten sam problem. Poprawka polega na dodaniu stackView.layoutIfNeeded()wewnątrz bloku animacji. Gdzie stackViewjest pojemnik z przedmiotami, które chcesz ukryć.

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)

Nie jestem pewien, dlaczego nagle jest to problem w iOS 11, ale szczerze mówiąc, zawsze było to zalecane podejście.

Springham
źródło
1
Jesteś bohaterem: D
Infinity James
5
Odpowiednia nazwa również „Springham” 😆
Infinity James
4
W iOS <= 10 występował błąd hiddenpolegający na tym, że ustawienie właściwości UIStackViewa subvieww bloku animacji było w niektórych przypadkach ignorowane, więc najlepiej jest zmienić to poza nim, tuż przed animacją.
Iulian Onofrei,
2
Może to być nieporozumienie z mojej strony, ale nie wydaje się, aby dokumentacja view.layoutIfNeeded()zaktualizowała pozycję innych widoków w StackView, czego chcemy. developer.apple.com/documentation/uikit/uiview/…
A Springham
6
view.layoutIfNeeded () jest w porządku, jednak wywołanie view.isHidden = true, jeśli widok jest już ukryty (lub odwrotnie), przerywa działanie. Dlatego sprawdź, czy widok nie jest już stanem ukrytym, który chcesz zmienić. if (view.isHidden == true) {view.isHidden = false}
glemoulant,
5

Rozszerzenie 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
źródło
5
U mnie poprawka polegała na sprawdzeniu self.isHiddeni nie ustawianiu wartości, jeśli jest już taka sama.
richy
1
może to być jedna funkcja o nazwie toggleAnimated (w ..., show: Bool). ponieważ zmienia się tylko jedna linia :) plus to nie zadziałało dla mnie: s
Jean Raymond Daher
Tak, dwie funkcje byłyby cukrem syntaktycznym po wykonaniu pojedynczej funkcji
ergunkocak
4

Wspomniano już o tym w komentarzach do zaakceptowanej odpowiedzi, ale to był mój problem i nie ma go w żadnej z odpowiedzi, więc:

Upewnij się, że nigdy nie włączasz isHidden = truewidoku, który jest już ukryty. Spowoduje to zepsucie widoku stosu.

jimpic
źródło
To był mój problem i nie musiałem dzwonić, layoutIfNeededwięc zastanawiam się, czy to powinna być prawidłowa odpowiedź.
B Roy Dawson
3

Chcę udostępnić tę funkcję, która jest dobra do ukrywania i pokazywania wielu widoków UIStackView, ponieważ cały kod, którego użyłem wcześniej, nie działał płynnie, ponieważ trzeba usunąć animację z niektórych warstw:

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.
źródło
To również rozwiązało problem braku zanikania / zanikania widoków. Piękny!
Petr Fiala,
3

Rozszerzenie do ukrywania / pokazywania pojedynczych elementów

Nie jest to w 100% powiązane, ale jeśli szukasz zwięzłego sposobu na ukrycie UIViewelementów singla (w widoku stosu lub gdziekolwiek indziej), możesz użyć tego prostego rozszerzenia, które zrobiłem:

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

Używam go do wygodnego ukrywania / pokazywania elementów z animacją w widoku stosu z pojedynczym wierszem kodu. Przykład:

validatableButton.isHiddenAnimated(value: false)
Alessandro Francucci
źródło
1

Mam nadzieję, że zaoszczędzi to innym kilku godzin frustracji.

Animowanie ukrywania ORAZ wyświetlania wielu podglądów podrzędnych UIStackView w tym samym czasie to bałagan.

W niektórych przypadkach zmiany .isHidden w blokach animacji są wyświetlane poprawnie do następnej animacji, a następnie .isHidden jest ignorowane. Jedyną niezawodną sztuczką, jaką znalazłem, jest powtórzenie instrukcji .isHidden w sekcji uzupełniania bloku animacji.

    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
źródło