iOS

[iOS] AutoLayout으로 애니메이션 구현하기

felix-mr 2021. 7. 26. 15:27

안녕하세요🙇‍♂️

오늘은 AutoLayout 변경을 통해 애니메이션을 구현하는 방법에 대한 게시글을 작성해보려고 합니다.

예제를 구현하며 간단하게 방법에 대해서만 작성해볼 예정이에요!

 

오늘 작성할 글은 View의 렌더링 부분과 관련이 있고 이 부분에 대해서 어느 정도 이해가 필요한 내용입니다.

다음에 렌더링과 관련해서 글을 작성할 예정인데 아직은 작성되지 않았으니까

혹시 이해가 안되시는 분들은 자료를 참고하고 오시는게 좋을 것 같습니다.😅

 

자 그럼 시작해볼까요? 깔깔

 

Constraint의 Constant를 변경하는 방법

아래와 같이 뷰를 구성했다고 생각해봅시다.

 

 

이 상황에서 버튼을 눌렀을 때 검정색뷰를 없어지는 애니메이션을 추가하고 싶다면 어떻게 해야할까요?

아? 물론! 프레임 사이즈를 조절하는 등 여러 방법이 존재하겠지만!

 

우리는 제약조건을 통해 애니메이션을 주기로 했잖아요?

 

그럼 일단 제약조건을 바꿔줘야겠죠?

자 코드를 통해 제약조건을 수정해봅시다.

class ViewController: UIViewController {

  ... 생략 ...
  
  lazy var secondViewHeightConstraint = secondView.widthAnchor.constraint(equalToConstant: 100)
  
  override func viewDidLoad() {
    super.viewDidLoad()
    
    setupConstraints()
  }
  
  ... 생략 ...

  @objc private func buttonTapped(_ sender: UIButton) {
    UIViewPropertyAnimator.runningPropertyAnimator(withDuration: 0.7, delay: 0, options: []) {
      self.secondViewHeightConstraint.constant = 0
    }
  }
  
  private func setupConstraints() {
      ... 생략 ...
      
      secondViewHeightConstraint.isActive = true
      ... 생략 ...
  }
}

먼저 우리가 조정해야할 건 secondView(검정색 뷰)의 height겠죠?

초기설정은 height 크기를 100으로 설정해두고 isActive를 true로 설정해줍시다.

 

그리고 버튼을 눌렀을 때, constant를 0으로 변경해주는 코드를 UIViewPropertyAnimator를 통해 실행시켜 줍시다.

 

애니메이션 효과가 나타날까요?

결과를 확인해봅시다!

 

 

떼잉! 분명 애니메이션 코드에 넣어줬는데 부드럽게 애니메이션 보여지는 것이 아니라 팍 팍! 하고 나타나네요?

요 부분은 업데이트 사이클에 대한 부분을 학습해야 합니다. 물론 여기서는 자세하게 언급하지 않습니다!

 

업데이트 사이클에 의해 변경된 제약조건으로 다시 그려지는 것은 확인 되었습니다.

 

하지만 우리가 원하는 것은 부드러운 애니메이션이죠.

업데이트 사이클까지 기다리는 것이 아니라 sync하게 다시 그려져야 합니다.

 

그러기 위해서 layoutIfNeeded를 호출해줘야 합니다.

layoutIfNeeded는 호출되었을 때 업데이트 사이클까지 기다리지 않고 동기적으로 바로 레이아웃을 업데이트 해줍니다.

이 부분에 대해서는 처음 얘기했듯이 나중에 더 자세하게 작성해보도록 할께요! 🤥

 

자 layoutIfNeeded를 호출해봅시다!

코드는 변경되어야겠죠?

 

class ViewController: UIViewController {

  ... 생략 ...
  
  private var isButtonTapped = false
  lazy var secondViewHeightConstraint = secondView.widthAnchor.constraint(equalToConstant: 100)
  
  override func viewDidLoad() {
    super.viewDidLoad()
    
    setupConstraints()
  }
  
  ... 생략 ...

  @objc private func buttonTapped(_ sender: UIButton) {
    isTapped.toggle()
    secondViewHeightConstraint.constant = isTapped ? 0 : 100
    
    UIViewPropertyAnimator.runningPropertyAnimator(withDuration: 0.7, delay: 0, options: []) {
      self.view.layoutIfNeeded()
    }
  }
  
  private func setupConstraints() {
      ... 생략 ...
      
      secondViewHeightConstraint.isActive = true
      ... 생략 ...
  }
}

 

isButtonTapped라는 Bool Property를 추가해서 애니메이션이 계속 나타나게 해봤습니다!

바뀐 코드에서는 view.layoutIfNeeded()를 애니메이션 클로져 안에서 호출해줬습니다.

 

이 코드를 통해 제약조건의 constant가 변경되고 애니메이션 클로져에서 동기적으로 레이아웃 업데이트가 일어납니다.

 

실행시켜 봅시다요!

 

이야! 아주 이쁘게 애니메이션을 보여주네요! ㅎㅎ

 

 

또 다른 방법으로 애니메이션 해봅시다!

 

 

Activate / Deactivate를 이용하는 방법

이번 방법은 좀 더 다양한 애니메이션을 보여줄 수 있는 방법입니다.

 

보통 Anchor를 통해 제약조건을 설정할 때,

 

// 1
secondView.widthAnchor.constraint(equalToConstant: 100).isActive = true
secondView.heightAnchor.constraint(equalToConstant: 100).isActive = true

// 2
NSLayoutConstaraint.activate([
  secondView.widthAnchor.constraint(equalToConstant: 100),
  secondView.heightAnchor.constraint(equalToConstant: 100)
])

이런식으로 하잖아요?

 

이 isActive 또는 activate는 해당 제약조건을 실행? active상태로 만들겠다는 것이죠?

 

그럼 기존 제약조건을을 Deactive 상태로 바꾸고 다른 제약조건을 Active상태로 바꿔준다면?

그러면 이것을 통해서도 애니메이션을 할 수 있지 않을까요? 띠용?

 

자 위의 예제를 재사용해서 다시 코드를 확인해봅시다!

 

class ViewController: UIViewController {

  ... 생략 ...
  
  lazy var originConstraints = [
    secondView.topAnchor.constraint(equalTo: firstView.bottomAnchor, constant: 10),
    thirdView.topAnchor.constraint(equalTo: secondView.bottomAnchor, constant: 10),
    thirdView.widthAnchor.constraint(equalTo: firstView.widthAnchor, multiplier: 1),
  ]
  lazy var updatedConstraints = [
    secondView.topAnchor.constraint(equalTo: thirdView.bottomAnchor, constant: 10),
    thirdView.topAnchor.constraint(equalTo: firstView.bottomAnchor, constant: 10),
    thirdView.widthAnchor.constraint(equalTo: firstView.widthAnchor, multiplier: 0.7),
  ]
  
  override func viewDidLoad() {
    super.viewDidLoad()
    
    setupConstraints()
  }
  
  ... 생략 ...

  @objc private func buttonTapped(_ sender: UIButton) {
    isTapped.toggle()
    NSLayoutConstraint.deactivate(isTapped ? originConstraints : updatedConstraints)
    NSLayoutConstraint.activate(isTapped ? updatedConstraints : originConstraints)
    
    UIViewPropertyAnimator.runningPropertyAnimator(withDuration: 0.7, delay: 0, options: []) {
      self.view.layoutIfNeeded()
    }
  }
  
  private func setupConstraints() {
    ... 생략 ...
      
    NSLayoutConstraint.activate(originConstraints)
    NSLayoutConstraint.activate([
      ... 생략 ...
    ])
    
      ... 생략 ...
  }
}

 

오옹! 이번에는 originConstraints와 updatedConstraints가 선언돼 있네요.

 

이름으로 봤을 때, originConstriants는 기존 제약조건들일 것이고,

updatedConstraints는 변경되는 제약조건인 것 같아요?

 

버튼을 tap했을 때 originConstraints와 updatedConstraints가 번갈아가며 deactivate / activate가 되고,

마찬가지로 애니메이션의 클로져에서 view.layoutIfNeeded가 호출됩니다.

 

선언된 제약조건들을 하나하나 다 설명하면 쓸데없이 길어질 것 같으니까

같이 실행화면을 확인해봐요! 🤪

 

 

🎉👏👏👏👏👏🎉

오홍홍! constant만 조절하는 것보다 좀 더 이쁘고 화려해 보이죠?

만족스럽습니다 ㅎㅎ

 

특히 multiplier의 경우에는 read-only이기 때문에 deactivate / activate 를 통해 변경해야 합니다!

 

 

예제 관련해서는 제 깃헙에 올려놓았으니 참고하실분들은 확인해주세요 😁

https://github.com/fElix-MR/iOS_Example/tree/master/AutoLayoutAnimation

 

GitHub - fElix-MR/iOS_Example

Contribute to fElix-MR/iOS_Example development by creating an account on GitHub.

github.com

 

 

Conclusion

UI / UX는 모바일 환경에서는 정말 중요한 요소입니다.

애니메이션은 특히 사용자에게 좋은 경험을 제공해 줄 수 있습니다.

 

물론 깊게 들어가면 복잡한 애니메이션, 화려한 애니메이션, 어려운 부분들이 차고 넘쳤습니다.

하지만 간단하게 AutoLayout 애니메이션을 통해서도 이 감성들을 충분히 전달해 줄 수 있다고 생각합니다!

 

오늘 내용중에 layoutIfNeeded가 동기적이다 어쩌구 저쩌구 하면서 이해 못하신 분들도 있을 거라고 생각합니다!

빠른 시일내에 렌더링과 관련된 블로그 글을 게시하도록 하겠습니다!

 

 

이번 게시글은 다른 자료를 참고하지 않고 그냥 오직 Xcode와 제 머리로만 작성했기 때문에 혹시라도 이해가 안되시거나 오타 또는 틀린부분이 있다면 댓글로 지적 부탁드립니다!😅 (대충 그동안 열심히 했으니까 칭찬받고 싶다는 뜻)

 

감사합니다.