essayer d'animer une contrainte rapidement

242

J'ai un UITextField que je veux agrandir sa largeur lorsqu'il est tapé dessus. J'ai configuré les contraintes et je me suis assuré que la contrainte de gauche a la priorité inférieure à celle que j'essaie d'animer du côté droit.

Voici le code que j'essaie d'utiliser.

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

Cela fonctionne, mais cela semble se produire instantanément et il ne semble pas y avoir de mouvement. J'ai essayé de le régler 10 secondes pour m'assurer de ne rien manquer, mais j'ai obtenu les mêmes résultats.

nameInputConstraint est le nom de la contrainte que je contrôle glissée pour se connecter à ma classe depuis IB.

Merci d'avance pour votre aide!

icekomo
la source

Réponses:

666

Vous devez d'abord modifier la contrainte, puis animer la mise à jour.

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
la source
2
Quelqu'un peut-il expliquer comment / pourquoi cela fonctionne? L'appel à animationWithDuration sur n'importe quelle UIView animera n'importe quelle classe qui hérite d'UIView et modifie une valeur physique?
2016 nippon
3
L'API d'animation que vous mentionnez est utilisée pour animer les propriétés des vues et des calques. Ici, nous devons animer le changement de mise en page . C'est ce que nécessite la modification de la constante d'une contrainte de mise en page - la modification de la constante seule ne fait rien.
Mundi
2
@Jacky Vous vous trompez peut-être avec le raisonnement Objective-C en référence aux variables fortes et faibles. Les fermetures rapides sont différentes: une fois la fermeture terminée, il n'y a plus d'objet accroché à celui selfutilisé dans la fermeture.
Mundi
2
ne fonctionne pas dans mon cas, cela arrive tout d'abord, que faire
Shakti
13
Il m'a fallu une heure pour remarquer que je dois appeler layoutIfNeeded sur superview ...
Nominalista
28

SWIFT 4.x:

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

Exemple avec complétion:

self.mConstraint.constant = 100
UIView.animate(withDuration: 0.3, animations: {
        self.view.layoutIfNeeded()
    }, completion: {res in
        //Do something
})
Hadži Lazar Pešić
la source
2
ça ne marche pas j'ai fait ça UIView.animate (withDuration: 10, delay: 0, options: .curveEaseInOut, animations: {self.leftViewHeightConstraint.constant = 200 self.leftView.layoutIfNeeded ()}, complétion: nil)
saurabhgoyal795
1
Vous devez changer la contrainte puis animer.
JaredH
Bien que cet extrait de code puisse être la solution, y compris une explication aide vraiment à améliorer la qualité de votre message. N'oubliez pas que vous répondrez à la question pour les lecteurs à l'avenir, et ces personnes pourraient ne pas connaître les raisons de votre suggestion de code.
Narendra Jadhav
La ligne ci-dessus a fait ma journée :) Merci @Hadzi
Nrv
18

Il est très important de souligner que cela view.layoutIfNeeded()s'applique uniquement aux sous-vues de vue.

Par conséquent , pour animer la contrainte de vue, il est important d'appeler sur la vue à Animer superview comme suit:

    topConstraint.constant = heightShift

    UIView.animate(withDuration: 0.3) {

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

Un exemple pour une mise en page simple comme suit:

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
la source
Mise à jour d'une contrainte de hauteur sur Swift 4 et cela a fonctionné lors de l'appel de layoutIfNeeded sur la vue, mais pas sur la vue d'ensemble. Vraiment utile, merci!
Alekxos
C'est vraiment la bonne réponse. Si vous ne l'appelez pas sur la vue d'ensemble, votre vue passe simplement au nouvel emplacement. C'est une distinction très importante.
Bryan Deemer
11

Avec Swift 5 et iOS 12.3, selon vos besoins, vous pouvez choisir l'une des 3 façons suivantes afin de résoudre votre problème.


#1. Utilisation de UIViewla animate(withDuration:animations:)méthode de classe

animate(withDuration:animations:) a la déclaration suivante:

Animez les modifications apportées à une ou plusieurs vues à l'aide de la durée spécifiée.

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

Le code Playground ci-dessous montre une implémentation possible de animate(withDuration:animations:)afin d'animer le changement constant d'une contrainte de disposition automatique.

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. Utilisation de UIViewPropertyAnimatorl' init(duration:curve:animations:)initialiseur et de la startAnimation()méthode

init(duration:curve:animations:) a la déclaration suivante:

Initialise l'animateur avec une courbe de synchronisation UIKit intégrée.

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

Le code Playground ci-dessous montre une implémentation possible de init(duration:curve:animations:)et startAnimation()afin d'animer le changement constant d'une contrainte de disposition automatique.

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. Utilisation de UIViewPropertyAnimatorla runningPropertyAnimator(withDuration:delay:options:animations:completion:)méthode de classe

runningPropertyAnimator(withDuration:delay:options:animations:completion:) a la déclaration suivante:

Crée et renvoie un objet animateur qui commence immédiatement à exécuter ses animations.

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

Le code Playground ci-dessous montre une implémentation possible de runningPropertyAnimator(withDuration:delay:options:animations:completion:)afin d'animer le changement constant d'une contrainte de disposition automatique.

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
la source
1
Une si bonne réponse!
Bashta
@Imanou grande réponse, apprécie le détail! Ajoutez éventuellement une brève description des options et quelle est la différence? Ce serait vraiment utile pour moi et je suis sûr que les autres auront une phrase expliquant pourquoi je choisirais l'un plutôt que l'autre.
rayepps
3

Dans mon cas, je n'ai mis à jour que la vue personnalisée.

// 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()
}
Tanière
la source
-1

Regardez ça .

La vidéo dit que vous devez simplement ajouter self.view.layoutIfNeeded()comme suit:

UIView.animate(withDuration: 1.0, animations: {
       self.centerX.constant -= 75
       self.view.layoutIfNeeded()
}, completion: nil)
mossman252
la source