tratando de animar una restricción de forma rápida

242

Tengo un UITextField que quiero ampliar su ancho cuando se toca. Configuré las restricciones y me aseguré de que la restricción de la izquierda tenga la prioridad más baja que la que estoy tratando de animar en el lado derecho.

Aquí está el código que estoy tratando de usar.

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

Esto funciona, pero parece ocurrir instantáneamente y no parece haber ningún movimiento. Traté de configurarlo 10 segundos para asegurarme de que no me faltaba nada, pero obtuve los mismos resultados.

nameInputConstraint es el nombre de la restricción que controlo arrastrado para conectarme a mi clase desde IB.

Gracias de antemano por su ayuda!

icekomo
fuente

Respuestas:

666

Primero debe cambiar la restricción y luego animar la actualización.

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
fuente
2
¿Alguien puede explicar cómo / por qué funciona esto? ¿Llamar a animationWithDuration en cualquier UIView animará cualquier clase que herede de UIView y cambie un valor físico?
nipponese
3
La API de animación que menciona se utiliza para animar las propiedades de las vistas y las capas. Aquí tenemos que animar el cambio de diseño . Eso es lo que requiere cambiar la constante de una restricción de diseño: cambiar la constante solo no hace nada.
Mundi
2
@Jacky Quizás esté confundiendo esto con el razonamiento de Objective-C en referencia a variables fuertes y débiles. Los cierres rápidos son diferentes: una vez que finaliza el cierre, no hay ningún objeto que se aferre al selfutilizado en el cierre.
Mundi
2
no funciona en mi caso, sucede primero, qué hacer
Shakti
13
Me tomó una hora darme cuenta, que tengo que llamar a layoutIfNeeded en la supervista ...
Nominalista
28

SWIFT 4.x:

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

Ejemplo con finalización:

self.mConstraint.constant = 100
UIView.animate(withDuration: 0.3, animations: {
        self.view.layoutIfNeeded()
    }, completion: {res in
        //Do something
})
Hadži Lazar Pešić
fuente
2
no funciona He hecho esto UIView.animate (withDuration: 10, delay: 0, options: .curveEaseInOut, animaciones: {self.leftViewHeightConstraint.constant = 200 self.leftView.layoutIfNeeded ()}, complete: nil)
saurabhgoyal795
1
Tienes que cambiar la restricción y luego animar.
JaredH
Si bien este fragmento de código puede ser la solución, incluir una explicación realmente ayuda a mejorar la calidad de su publicación. Recuerde que está respondiendo la pregunta para los lectores en el futuro, y que esas personas podrían no conocer los motivos de su sugerencia de código.
Narendra Jadhav
La línea anterior hizo mi día :) Gracias @Hadzi
Nrv
18

Es muy importante señalar que solo se view.layoutIfNeeded()aplica a las subvistas de vista.

Por lo tanto, para animar la restricción de la vista, es importante invocarla en la vista general de la vista para animar de la siguiente manera:

    topConstraint.constant = heightShift

    UIView.animate(withDuration: 0.3) {

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

Un ejemplo para un diseño simple de la siguiente manera:

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
fuente
La actualización de una restricción de altura en Swift 4 y esto funcionó al llamar a layoutIfNeeded en la vista, pero no la supervista no. Muy útil, gracias!
Alekxos
Esta es realmente la respuesta correcta. Si no lo llama en supervista, su vista simplemente salta a la nueva ubicación. Esta es una distinción muy importante.
Bryan Deemer
11

Con Swift 5 y iOS 12.3, según sus necesidades, puede elegir una de las 3 formas siguientes para resolver su problema.


# 1 Usando UIViewel animate(withDuration:animations:)método de clase

animate(withDuration:animations:) tiene la siguiente declaración:

Anime los cambios en una o más vistas usando la duración especificada.

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

El siguiente código de Playground muestra una posible implementación de animate(withDuration:animations:)para animar el cambio constante de una restricción de diseño automático.

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 UIViewPropertyAnimatorel init(duration:curve:animations:)iniciador y el startAnimation()método

init(duration:curve:animations:) tiene la siguiente declaración:

Inicializa el animador con una curva de sincronización UIKit incorporada.

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

El siguiente código de Playground muestra una posible implementación de init(duration:curve:animations:)y startAnimation()para animar el cambio constante de una restricción de diseño automático.

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 UIViewPropertyAnimatorel runningPropertyAnimator(withDuration:delay:options:animations:completion:)método de clase

runningPropertyAnimator(withDuration:delay:options:animations:completion:) tiene la siguiente declaración:

Crea y devuelve un objeto animador que comienza a ejecutar sus animaciones de inmediato.

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

El siguiente código de Playground muestra una posible implementación de runningPropertyAnimator(withDuration:delay:options:animations:completion:)para animar el cambio constante de una restricción de diseño automático.

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
fuente
1
¡Qué gran respuesta!
Bashta
@Imanou gran respuesta, aprecio los detalles! ¿Posiblemente agregar una breve descripción de las opciones y cuál es la diferencia? Sería realmente útil para mí y estoy seguro de que otros tendrán una oración sobre por qué elegiría uno sobre otro.
rayepps
3

En mi caso, solo actualicé la vista personalizada.

// 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()
}
Guarida
fuente
-1

Mira este .

El video dice que solo necesita agregar self.view.layoutIfNeeded()lo siguiente:

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