próba szybkiej animacji ograniczenia


242

Mam pole UITextField, które chcę powiększyć po naciśnięciu. Ustawiłem ograniczenia i upewniłem się, że ograniczenie po lewej ma niższy priorytet niż ten, który próbuję animować po prawej stronie.

Oto kod, którego próbuję użyć.

  // move the input box
    UIView.animateWithDuration(10.5, animations: {
        self.nameInputConstraint.constant = 8
        }, completion: {
            (value: Bool) in
            println(">>> move const")
    })

To działa, ale wydaje się, że dzieje się to natychmiast i wydaje się, że nie ma żadnego ruchu. Próbowałem ustawić to na 10 sekund, aby upewnić się, że niczego nie umknę, ale uzyskałem te same wyniki.

nameInputConstraint to nazwa ograniczenia, które kontroluję przeciągane, aby połączyć się z moją klasą z IB.

Z góry dziękuję za pomoc!


Odpowiedzi:


666

Najpierw musisz zmienić ograniczenie, a następnie animować aktualizację.

self.nameInputConstraint.constant = 8

Swift 2

UIView.animateWithDuration(0.5) {
    self.view.layoutIfNeeded()
}

Swift 3, 4, 5

UIView.animate(withDuration: 0.5) {
    self.view.layoutIfNeeded()
}

2
Czy ktoś może wyjaśnić, jak / dlaczego to działa? Wywołanie animacjiWithDuration na dowolnym UIView będzie animować każdą klasę, która dziedziczy z UIView i zmienia wartość fizyczną?
nipponese

3
Wspomniany interfejs API animacji służy do animowania właściwości widoków i warstw. Tutaj musimy animować zmianę układu . To właśnie wymaga zmiany stałej ograniczenia układu - sama zmiana stałej nic nie robi.
Mundi,

2
@Jacky Być może mylisz to z rozumowaniem celu C w odniesieniu do silnych i słabych zmiennych. Szybkie zamknięcia są różne: po zakończeniu zamknięcia żaden przedmiot nie trzyma się tego, co selfużyte w zamknięciu.
Mundi

2
nie działa w moim przypadku, najpierw dzieje się, co robić
Shakti

13
Godzinę zajęło mi zauważenie, że muszę zadzwonić do układu Jeśli zajrzysz do podglądu ...
Nominalista

28

SWIFT 4.x:

self.mConstraint.constant = 100.0
UIView.animate(withDuration: 0.3) {
        self.view.layoutIfNeeded()
}

Przykład z zakończeniem:

self.mConstraint.constant = 100
UIView.animate(withDuration: 0.3, animations: {
        self.view.layoutIfNeeded()
    }, completion: {res in
        //Do something
})

2
nie działa Zrobiłem to UIView.animate (zCzas trwania: 10, opóźnienie: 0, opcje: .curveEaseInOut, animacje: {self.leftViewHeightConstraint.constant = 200 self.leftView.layoutIfNeeded ()}, zakończenie: zero)
saurabhgoyal795

1
Musisz zmienić ograniczenie, a następnie animować.
JaredH

Ten fragment kodu może być rozwiązaniem, ale wyjaśnienie naprawdę pomaga poprawić jakość posta. Pamiętaj, że w przyszłości odpowiadasz na pytanie dla czytelników, a ci ludzie mogą nie znać przyczyn Twojej sugestii kodu.
Narendra Jadhav

Powyższa linia sprawiła, że ​​mój dzień :) Dziękuję @Hadzi
Nrv

18

Bardzo ważne jest, aby zaznaczyć, że view.layoutIfNeeded()dotyczy to tylko widoków podrzędnych.

Dlatego w celu animowania ograniczenia widoku ważne jest, aby wywołać go w widoku kontrolnym animacji w następujący sposób:

    topConstraint.constant = heightShift

    UIView.animate(withDuration: 0.3) {

        // request layout on the *superview*
        self.view.superview?.layoutIfNeeded()
    }

Przykład prostego układu w następujący sposób:

class MyClass {

    /// Container view
    let container = UIView()
        /// View attached to container
        let view = UIView()

    /// Top constraint to animate
    var topConstraint = NSLayoutConstraint()


    /// Create the UI hierarchy and constraints
    func createUI() {
        container.addSubview(view)

        // Create the top constraint
        topConstraint = view.topAnchor.constraint(equalTo: container.topAnchor, constant: 0)


        view.translatesAutoresizingMaskIntoConstraints = false

        // Activate constaint(s)
        NSLayoutConstraint.activate([
           topConstraint,
        ])
    }

    /// Update view constraint with animation
    func updateConstraint(heightShift: CGFloat) {
        topConstraint.constant = heightShift

        UIView.animate(withDuration: 0.3) {

            // request layout on the *superview*
            self.view.superview?.layoutIfNeeded()
        }
    }
}

Aktualizacja ograniczenia wysokości w Swift 4 i to zadziałało podczas wywoływania layoutIfNeeded w widoku, ale nie działał superview. Naprawdę pomocna, dzięki!
Alekxos,

To jest naprawdę poprawna odpowiedź. Jeśli nie nazwiesz go w widoku podglądu, widok po prostu przeskoczy do nowej lokalizacji. To bardzo ważne rozróżnienie.
Bryan Deemer

11

W Swift 5 i iOS 12.3, w zależności od potrzeb, możesz wybrać jeden z 3 następujących sposobów rozwiązania problemu.


# 1. Korzystanie UIViewz animate(withDuration:animations:)metody klasy

animate(withDuration:animations:) ma następującą deklarację:

Animuj zmiany w jednym lub kilku widokach, używając określonego czasu trwania.

class func animate(withDuration duration: TimeInterval, animations: @escaping () -> Void)

Poniższy kod Playground pokazuje możliwą implementację animate(withDuration:animations:)w celu animacji ciągłej zmiany ograniczenia Auto Layout.

import UIKit
import PlaygroundSupport

class ViewController: UIViewController {

    let textView = UITextView()
    lazy var heightConstraint = textView.heightAnchor.constraint(equalToConstant: 50)

    override func viewDidLoad() {
        view.backgroundColor = .white
        view.addSubview(textView)

        textView.backgroundColor = .orange
        textView.isEditable = false
        textView.text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."

        textView.translatesAutoresizingMaskIntoConstraints = false
        textView.topAnchor.constraint(equalToSystemSpacingBelow: view.layoutMarginsGuide.topAnchor, multiplier: 1).isActive = true
        textView.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor).isActive = true
        textView.trailingAnchor.constraint(equalTo: view.layoutMarginsGuide.trailingAnchor).isActive = true
        heightConstraint.isActive = true

        let tapGesture = UITapGestureRecognizer(target: self, action: #selector(doIt(_:)))
        textView.addGestureRecognizer(tapGesture)
    }

    @objc func doIt(_ sender: UITapGestureRecognizer) {
        heightConstraint.constant = heightConstraint.constant == 50 ? 150 : 50
        UIView.animate(withDuration: 2) {
            self.view.layoutIfNeeded()
        }
    }

}

PlaygroundPage.current.liveView = ViewController()

# 2. Korzystanie UIViewPropertyAnimatorz init(duration:curve:animations:)inicjalizatora i startAnimation()metody

init(duration:curve:animations:) ma następującą deklarację:

Inicjuje animatora za pomocą wbudowanej krzywej synchronizacji UIKit.

convenience init(duration: TimeInterval, curve: UIViewAnimationCurve, animations: (() -> Void)? = nil)

Poniższy kod Playground pokazuje możliwą implementację init(duration:curve:animations:)i startAnimation()w celu animowania stałej zmiany ograniczenia Auto Layout.

import UIKit
import PlaygroundSupport

class ViewController: UIViewController {

    let textView = UITextView()
    lazy var heightConstraint = textView.heightAnchor.constraint(equalToConstant: 50)

    override func viewDidLoad() {
        view.backgroundColor = .white
        view.addSubview(textView)

        textView.backgroundColor = .orange
        textView.isEditable = false
        textView.text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."

        textView.translatesAutoresizingMaskIntoConstraints = false
        textView.topAnchor.constraint(equalToSystemSpacingBelow: view.layoutMarginsGuide.topAnchor, multiplier: 1).isActive = true
        textView.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor).isActive = true
        textView.trailingAnchor.constraint(equalTo: view.layoutMarginsGuide.trailingAnchor).isActive = true
        heightConstraint.isActive = true

        let tapGesture = UITapGestureRecognizer(target: self, action: #selector(doIt(_:)))
        textView.addGestureRecognizer(tapGesture)
    }

    @objc func doIt(_ sender: UITapGestureRecognizer) {
        heightConstraint.constant = heightConstraint.constant == 50 ? 150 : 50
        let animator = UIViewPropertyAnimator(duration: 2, curve: .linear, animations: {
            self.view.layoutIfNeeded()
        })
        animator.startAnimation()
    }

}

PlaygroundPage.current.liveView = ViewController()

# 3. Korzystanie UIViewPropertyAnimatorz runningPropertyAnimator(withDuration:delay:options:animations:completion:)metody klasy

runningPropertyAnimator(withDuration:delay:options:animations:completion:) ma następującą deklarację:

Tworzy i zwraca obiekt animatora, który natychmiast uruchamia animacje.

class func runningPropertyAnimator(withDuration duration: TimeInterval, delay: TimeInterval, options: UIViewAnimationOptions = [], animations: @escaping () -> Void, completion: ((UIViewAnimatingPosition) -> Void)? = nil) -> Self

Poniższy kod Playground pokazuje możliwą implementację runningPropertyAnimator(withDuration:delay:options:animations:completion:)w celu animacji ciągłej zmiany ograniczenia Auto Layout.

import UIKit
import PlaygroundSupport

class ViewController: UIViewController {

    let textView = UITextView()
    lazy var heightConstraint = textView.heightAnchor.constraint(equalToConstant: 50)

    override func viewDidLoad() {
        view.backgroundColor = .white
        view.addSubview(textView)

        textView.backgroundColor = .orange
        textView.isEditable = false
        textView.text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."

        textView.translatesAutoresizingMaskIntoConstraints = false
        textView.topAnchor.constraint(equalToSystemSpacingBelow: view.layoutMarginsGuide.topAnchor, multiplier: 1).isActive = true
        textView.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor).isActive = true
        textView.trailingAnchor.constraint(equalTo: view.layoutMarginsGuide.trailingAnchor).isActive = true
        heightConstraint.isActive = true

        let tapGesture = UITapGestureRecognizer(target: self, action: #selector(doIt(_:)))
        textView.addGestureRecognizer(tapGesture)
    }

    @objc func doIt(_ sender: UITapGestureRecognizer) {
        heightConstraint.constant = heightConstraint.constant == 50 ? 150 : 50
        UIViewPropertyAnimator.runningPropertyAnimator(withDuration: 2, delay: 0, options: [], animations: {
            self.view.layoutIfNeeded()
        })
    }

}

PlaygroundPage.current.liveView = ViewController()

1
Taka świetna odpowiedź!
Bashta

@Imanou świetna odpowiedź, docenić szczegóły! Ewentualnie dodaj krótki opis opcji i jaka jest różnica? Byłby dla mnie naprawdę pomocny i jestem pewien, że inni mają zdanie na temat tego, dlaczego wybrałem się nawzajem.
rayepps

3

W moim przypadku zaktualizowałem tylko widok niestandardowy.

// DO NOT LIKE THIS
customView.layoutIfNeeded()    // Change to view.layoutIfNeeded()
UIView.animate(withDuration: 0.5) {
   customViewConstraint.constant = 100.0
   customView.layoutIfNeeded() // Change to view.layoutIfNeeded()
}

-1

Zobacz to .

Film mówi, że musisz po prostu dodać self.view.layoutIfNeeded():

UIView.animate(withDuration: 1.0, animations: {
       self.centerX.constant -= 75
       self.view.layoutIfNeeded()
}, completion: nil)
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.