관리 메뉴

김종권의 iOS 앱 개발 알아가기

[iOS - swift] 화면 전환 애니메이션 커스텀 방법 (UIPercentDrivenInteractiveTransition, UIViewControllerAnimatedTransitioning, UIViewControllerTransitioningDelegate) 본문

iOS 응용 (swift)

[iOS - swift] 화면 전환 애니메이션 커스텀 방법 (UIPercentDrivenInteractiveTransition, UIViewControllerAnimatedTransitioning, UIViewControllerTransitioningDelegate)

jake-kim 2022. 3. 20. 14:00

VC1 -> VC2 화면 전환 애니메이션 커스텀

화면전환 애니메이션 커스텀 아이디어

  • VC1 -> VC2로 화면전환 하는 경우, VC2 인스턴스의 화면전환 델리게이트를 conform하여 애니메이션을 넣어주는 것
  • delegate 부분
// ViewController.swift (=VC1)

@objc private func didTapNextButton() {
  let vc2 = VC2()
  vc2.transitioningDelegate = self // <- 델리게이트 conform
  vc2.modalPresentationStyle = .fullScreen
  self.present(vc2, animated: true)
}
  • delegate는 UIViewControllerTransitioningDelegate
    • present와 dismiss 따로 구현
    • 보통 UIViewControllerTransitioningDelegate 프로토콜을 구현하는 커스텀 클래스 인스턴스를 주입
extension ViewController: UIViewControllerTransitioningDelegate {
  func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
    MyPresentTransition()
  }
  func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
    nil
  }
}

UIViewControllerTransitioningDelegate 구현하는 클래스

  • UIPercentDrivenInteractiveTransition는 NSObject 형태의 클래스가 필요
import UIKit

final class MyPresentTransition: NSObject {
  
}
  • 2가지 메소드 구현
extension MyPresentTransition: UIViewControllerAnimatedTransitioning {
  // 애니메이션 동작 시간
  func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
  }
  
  // 애니메이션 정의
  func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
  }
}
  • 애니메이션에 사용될 상수 선언
  private enum Constants {
    static let duration = 1.0
    static let affineTransform = CGAffineTransform(scaleX: 0.5, y: 0.5)
  }
  • animateTransition 메소드 내부 구현
    1. 애니메이션이 실행 될 toView와 그의 컨테이너인 containerView 획득
    2. 애니메이션 시작할 때의 toView의 위치를 containerView와 동일하게 설정
    3. 커스텀 애니메이션 정의
      * 아래에서 내려오는 animateKeyframes 애니메이션 사용 방법은 이전 포스팅 글 참고
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
  // 1. 애니메이션에 적용할 뷰 획득
  let containerView = transitionContext.containerView
  guard let toView = transitionContext.view(forKey: .to) else { return }
  containerView.addSubview(toView)
  containerView.bringSubviewToFront(toView)
  
  // 2. 초기 위치 설정
  toView.translatesAutoresizingMaskIntoConstraints = false
  NSLayoutConstraint.activate([
    toView.leftAnchor.constraint(equalTo: containerView.leftAnchor),
    toView.rightAnchor.constraint(equalTo: containerView.rightAnchor),
    toView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
    toView.topAnchor.constraint(equalTo: containerView.topAnchor),
  ])
  
  // 3. 커스텀 애니메이션 정의
  UIView.animateKeyframes(
    withDuration: Constants.duration,
    delay: 0,
    animations: {
      UIView.addKeyframe(
        withRelativeStartTime: 0 / 2,
        relativeDuration: 1 / 2,
        animations: {
          toView.transform = Constants.affineTransform
        }
      )
      UIView.addKeyframe(
        withRelativeStartTime: 1 / 2,
        relativeDuration: 1 / 2,
        animations: {
          toView.transform = .identity
        }
      )
    },
    completion: { transitionContext.completeTransition($0) }
  )
}
  • 완성된 MyPresentTransition 클래스를 화면전환할 때 주입
// ViewController.swift (=VC1)

@objc private func didTapNextButton() {
  let vc2 = VC2()
  vc2.transitioningDelegate = self // <- 델리게이트 conform
  vc2.modalPresentationStyle = .fullScreen
  self.present(vc2, animated: true)
}

...

extension ViewController: UIViewControllerTransitioningDelegate {
  func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
    MyPresentTransition() // <- 사용
  }
  func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
    nil
  }
}

* dismiss에도 커스텀 애니메이션을 구현한다면, toView가 아닌 fromView를 얻어서 구현

import UIKit

final class MyDismissTransition: NSObject {

}

extension MyDismissTransition: UIViewControllerAnimatedTransitioning {
  func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
    0.5
  }
  
  func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
    // 사라지는 뷰
    guard let fromView = transitionContext.view(forKey: .from) else { return }
    UIView.animate(
      withDuration: 0.5,
      animations: { fromView.alpha = 0 },
      completion: { completed in transitionContext.completeTransition(completed) }
    )
  }
}

* 전체 코드: https://github.com/JK0369/ExTransitionAnimation


* 참고

https://www.raywenderlich.com/2925473-ios-animation-tutorial-custom-view-controller-presentation-transitions

Comments