본문 바로가기
iOS/이슈

[iOS Issue] constraint으로 animate 하기 전, layoutIfNeeded를 꼭 호출해야함

by Sky Titan 2022. 5. 21.
728x90
 

How to animate a UIImage constraints in Swift 4

I am making a card app and I need to make an animation so that a card would change its constraints to move to another place. How would I do this for a UIImage.

stackoverflow.com

UIView.animate에서 constraint을 변경

 예전에 글을 올린 적이 있지만 UIView.animate 내부에서 constraint의 constant 값을 수정해서 animation을 실행할 시, layoutIfNeeded를 클로저 내부에 꼭 호출을 해야 UI가 업데이트 되면서 애니메이션이 동작한다.

 

 그런데 이 때 constant를 수정하기 전, 즉 애니메이션을 실행하기 전에 layoutIfNeeded를 한 번 호출해서 layout의 상태를 업데이트 시켜놓은 뒤에, animation을 실행하는 것이 권고되는 데, 왜냐하면 layout이 업데이트가 되어있지 않음은 의도치 않은 사이드 이펙트를 만들 수 있기 때문이다.

 

 아래와 같은 코드가 있다고 치자.

import UIKit

class ViewController: UIViewController {
    
    @IBOutlet weak var redView: UIView!
    @IBOutlet weak var blueView: UIView!
    @IBOutlet weak var redViewTopConstraint: NSLayoutConstraint!
    @IBOutlet weak var blueViewBottomConstraint: NSLayoutConstraint!
    
    override func viewDidLoad() {
        super.viewDidLoad()
     
    }
    
    @IBAction func animation(_ sender: Any) {
        // animation과 별개로 blueView의 layout을 수정하길 원한다.
        blueViewBottomConstraint.constant = 0
        
        // redView에게만 animation을 적용하길 원한다.
        UIView.animate(withDuration: 1.0, delay: 0, options: [], animations: {
            self.redViewTopConstraint.constant = 0
            self.view.layoutIfNeeded()
        }, completion: nil)
    }
    
}

 

 다음과 같은 상태에서 button을 클릭했을 시 redView의 상단으로 완전히 달라붙는 애니메이션을 실행하려고 한다. 근데 보다시피 코드에서 UIView.animate이전에 blueView의 constraint을 수정해서 blueView의 layout을 업데이트를 알리는 dirtyflag가 바뀌어져 있는 상황이다. 이 상황에서 애니메이션을 실행하면 아래와 같이 blueView에도 animation이 적용된다.

 

 updateCycle상 아직 업데이트 되지 않은 layout이 있다면 animate 블록 안에서 layoutIfNeeded를 호출할 때 같이 업데이트 되기 때문에 발생하는 현상이다.

 그렇기에 아래와 같이 animate를 호출하기 전에 layoutIfNeeded를 호출해서 layout들을 업데이트해놓고 animate를 호출하는 것이 권고된다.

 

import UIKit

class ViewController: UIViewController {
    
    @IBOutlet weak var redView: UIView!
    @IBOutlet weak var blueView: UIView!
    @IBOutlet weak var redViewTopConstraint: NSLayoutConstraint!
    @IBOutlet weak var blueViewBottomConstraint: NSLayoutConstraint!
    
    override func viewDidLoad() {
        super.viewDidLoad()
     
    }
    
    
    @IBAction func animation(_ sender: Any) {
        // animation과 별개로 blueView의 layout을 수정하길 원한다.
        blueViewBottomConstraint.constant = 0
        view.layoutIfNeeded() //layout을 모두 업데이트 시켜놓는다.
        
        // redView에게만 animation을 적용하길 원한다.
        UIView.animate(withDuration: 1.0, delay: 0, options: [], animations: {
            self.redViewTopConstraint.constant = 0
            self.view.layoutIfNeeded()
        }, completion: nil)
    }
    
}

 

728x90

댓글