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!

icekomo
źródło
3
możliwy duplikat Jak animować zmiany ograniczeń?
emem

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()
}
Mundi
źródło
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
})
Hadži Lazar Pešić
źródło
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()
        }
    }
}
Stéphane de Luca
źródło
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()
Imanou Petit
źródło
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()
}
Legowisko
źródło
-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)
mossman252
źródło