tentando animar uma restrição rapidamente

242

Eu tenho um UITextField que eu quero aumentar sua largura quando tocado. Configurei as restrições e verifiquei se a restrição à esquerda tem a prioridade mais baixa que a que estou tentando animar no lado direito.

Aqui está o código que estou tentando usar.

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

Isso funciona, mas parece acontecer instantaneamente e não parece haver nenhum movimento. Tentei definir 10 segundos para garantir que não perdesse nada, mas obtive os mesmos resultados.

nameInputConstraint é o nome da restrição que eu controlo arrastada para conectar-me à minha classe do IB.

Agradecemos desde já a sua ajuda!

icekomo
fonte
3
possível duplicação de Como animar alterações de restrição?
EMEM

Respostas:

665

Você precisa primeiro alterar a restrição e animar a atualização.

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
fonte
2
Alguém pode explicar como / por que isso funciona? Chamar animationWithDuration em qualquer UIView animará qualquer classe que herda de UIView e altera um valor físico?
Nipponese
3
A API de animação mencionada é usada para animar as propriedades de visualizações e camadas. Aqui precisamos animar a mudança no layout . É isso que exige a alteração da constante de uma restrição de layout - alterar a constante sozinha não faz nada.
Mundi
2
@ Jacky Talvez você esteja confundindo isso com o raciocínio Objective-C em referência a variáveis ​​fortes e fracas. Os fechamentos rápidos são diferentes: depois que o fechamento termina, não há nenhum objeto segurando o selfusado no fechamento.
Mundi
2
não funciona no meu caso, que isso aconteça primeiro, o que fazer
Shakti
13
Levei uma hora para o aviso prévio, que eu tenho que chamar layoutIfNeeded em superview ...
Nominalista
28

SWIFT 4.x:

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

Exemplo com conclusão:

self.mConstraint.constant = 100
UIView.animate(withDuration: 0.3, animations: {
        self.view.layoutIfNeeded()
    }, completion: {res in
        //Do something
})
Hadži Lazar Pešić
fonte
2
não está funcionando, eu fiz isso UIView.animate (withDuration: 10, delay: 0, opções: .curveEaseInOut, animações: {self.leftViewHeightConstraint.constant = 200 self.leftView.layoutIfNeeded ()}, conclusão: zero)
saurabhgoyal795
1
Você precisa alterar a restrição e depois animar.
JaredH
Embora esse snippet de código possa ser a solução, incluir uma explicação realmente ajuda a melhorar a qualidade da sua postagem. Lembre-se de que você está respondendo à pergunta dos leitores no futuro e essas pessoas podem não saber os motivos da sua sugestão de código.
Narendra Jadhav
A linha acima fez o meu dia :) Obrigado @Hadzi
Nrv 20/02
18

É muito importante ressaltar que view.layoutIfNeeded()se aplica apenas às subvisões de exibição.

Portanto, para animar a restrição de exibição, é importante chamá-la na superview de exibição para animar da seguinte maneira:

    topConstraint.constant = heightShift

    UIView.animate(withDuration: 0.3) {

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

Um exemplo para um layout simples da seguinte maneira:

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
fonte
A atualização de uma restrição de altura no Swift 4 e isso funcionou ao chamar layoutIfNeeded na visualização, mas não na superview. Realmente útil, obrigado!
Alekxos
Esta é realmente a resposta correta. Se você não ativar a superview, sua visualização será direcionada para o novo local. Isto é uma distinção muito importante.
Bryan Deemer
11

Com o Swift 5 e iOS 12.3, de acordo com suas necessidades, você pode escolher uma das três maneiras a seguir para resolver seu problema.


# 1 Usando UIViewo animate(withDuration:animations:)método de classe

animate(withDuration:animations:) tem a seguinte declaração:

Animar alterações para uma ou mais visualizações usando a duração especificada.

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

O código do Playground abaixo mostra uma possível implementação de animate(withDuration:animations:)para animar a mudança constante de uma restrição de Layout 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 UIViewPropertyAnimatoro init(duration:curve:animations:)inicializador e o startAnimation()método

init(duration:curve:animations:) tem a seguinte declaração:

Inicializa o animador com uma curva de tempo do UIKit embutida.

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

O código do Playground abaixo mostra uma possível implementação de init(duration:curve:animations:) e startAnimation()para animar a mudança constante de uma restrição de Layout 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 UIViewPropertyAnimatoro runningPropertyAnimator(withDuration:delay:options:animations:completion:)método de classe

runningPropertyAnimator(withDuration:delay:options:animations:completion:) tem a seguinte declaração:

Cria e retorna um objeto animador que começa a executar suas animações imediatamente.

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

O código do Playground abaixo mostra uma possível implementação de runningPropertyAnimator(withDuration:delay:options:animations:completion:)para animar a mudança constante de uma restrição de Layout 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
fonte
1
Que ótima resposta!
Bashta
@Imanou ótima resposta, aprecio os detalhes! Possivelmente, adicione uma breve descrição das opções e qual é a diferença? Seria realmente útil para mim e tenho certeza que outras pessoas terão uma frase sobre o motivo de escolher uma.
rayepps
3

No meu caso, atualizei apenas a exibição 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()
}
Den
fonte
-1

Veja isso .

O vídeo diz que você precisa adicionar apenas self.view.layoutIfNeeded()o seguinte:

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