cercando di animare un vincolo in tempi rapidi


242

Ho un UITextField che voglio ingrandire la sua larghezza quando toccato. Ho impostato i vincoli e mi sono assicurato che il vincolo a sinistra abbia la priorità inferiore rispetto a quello che sto cercando di animare sul lato destro.

Ecco il codice che sto cercando di usare.

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

Funziona, ma sembra accadere all'istante e non sembra esserci alcun movimento. Ho provato a impostarlo 10 secondi per assicurarmi che non mi mancasse nulla, ma ho ottenuto gli stessi risultati.

nameInputConstraint è il nome del vincolo che controllo trascinato per connettermi alla mia classe da IB.

Grazie per il vostro aiuto in anticipo!


Risposte:


666

È necessario innanzitutto modificare il vincolo e quindi animare l'aggiornamento.

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
Qualcuno può spiegare come / perché funziona? La chiamata di animationWithDuration su qualsiasi UIView animerà qualsiasi classe che eredita da UIView e modifica un valore fisico?
Nipponese

3
L'API di animazione menzionata viene utilizzata per animare le proprietà di viste e livelli. Qui dobbiamo animare il cambiamento nel layout . Questo è ciò che richiede cambiare la costante di un vincolo di layout: cambiare la costante da sola non fa nulla.
Mundi

2
@Jacky Forse stai sbagliando questo con il ragionamento Objective-C in riferimento a variabili forti e deboli. Le chiusure rapide sono diverse: una volta terminata la chiusura, non vi è alcun oggetto aggrappato a quello selfutilizzato nella chiusura.
Mundi,

2
non funziona nel mio caso, sta succedendo molto prima, cosa fare
Shakti,

13
Mi ci è voluta un'ora per notare che dovevo chiamare layout Se avessi bisogno di una supervista ...
Nominalista,

28

SWIFT 4.x:

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

Esempio con completamento:

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

2
non funziona Ho fatto questo UIView.animate (con Durata: 10, ritardo: 0, opzioni: .curveEaseInOut, animazioni: {self.leftViewHeightConstraint.constant = 200 self.leftView.layoutIfNeeded ()}, completamento: zero)
saurabhgoyal795

1
Devi modificare il vincolo e quindi animare.
JaredH

Mentre questo frammento di codice può essere la soluzione, inclusa una spiegazione aiuta davvero a migliorare la qualità del tuo post. Ricorda che stai rispondendo alla domanda per i lettori in futuro e quelle persone potrebbero non conoscere i motivi del tuo suggerimento sul codice.
Narendra Jadhav,

La riga sopra ha reso la mia giornata :) Grazie @Hadzi
Nrv

18

È molto importante sottolineare che view.layoutIfNeeded()vale solo per le viste secondarie della vista.

Pertanto, per animare il vincolo della vista, è importante chiamarlo nella superview vista da animare come segue:

    topConstraint.constant = heightShift

    UIView.animate(withDuration: 0.3) {

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

Un esempio per un layout semplice come segue:

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

Aggiornamento di un vincolo di altezza su Swift 4 e questo ha funzionato durante la chiamata layoutIfNecessario sulla vista ma non la superview no. Davvero utile, grazie!
Alekxos,

Questa è davvero la risposta corretta. Se non lo chiami in superview, la tua vista passa semplicemente alla nuova posizione. Questa è una distinzione molto importante.
Bryan Deemer,

11

Con Swift 5 e iOS 12.3, in base alle tue esigenze, puoi scegliere uno dei 3 seguenti modi per risolvere il tuo problema.


# 1. Usando UIView'sanimate(withDuration:animations:) metodo di classe

animate(withDuration:animations:) ha la seguente dichiarazione:

Animare le modifiche a una o più viste utilizzando la durata specificata.

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

Il codice Playground di seguito mostra una possibile implementazione di animate(withDuration:animations:)al fine di animare il cambiamento costante di un vincolo Layout automatico.

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. Usando UIViewPropertyAnimatorl' init(duration:curve:animations:)inizializzatore e il startAnimation()metodo

init(duration:curve:animations:) ha la seguente dichiarazione:

Inizializza l'animatore con una curva di temporizzazione UIKit integrata.

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

Il codice Playground di seguito mostra una possibile implementazione di init(duration:curve:animations:)e startAnimation()al fine di animare il cambiamento costante di un vincolo Layout automatico.

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. Usando UIViewPropertyAnimatoril runningPropertyAnimator(withDuration:delay:options:animations:completion:)metodo di classe

runningPropertyAnimator(withDuration:delay:options:animations:completion:) ha la seguente dichiarazione:

Crea e restituisce un oggetto animatore che inizia a eseguire immediatamente le sue animazioni.

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

Il codice Playground di seguito mostra una possibile implementazione di runningPropertyAnimator(withDuration:delay:options:animations:completion:)al fine di animare il cambiamento costante di un vincolo Layout automatico.

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
Una risposta così grande!
Bashta,

@Imanou ottima risposta, apprezza i dettagli! Eventualmente aggiungere una breve descrizione delle opzioni e qual è la differenza? Sarebbe davvero utile per me e sono sicuro per gli altri di avere una frase sul perché avrei scelto uno rispetto all'altro.
Rayepps,

3

Nel mio caso, ho aggiornato solo la vista personalizzata.

// 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

Guarda questo .

Il video dice che devi solo aggiungere self.view.layoutIfNeeded()come il seguente:

UIView.animate(withDuration: 1.0, animations: {
       self.centerX.constant -= 75
       self.view.layoutIfNeeded()
}, completion: nil)
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.