[UIView] 카드를 뒤집어보자 (transition animation)

2021. 10. 16. 13:37UIKit, SwiftUI, H.I.G

 

transition(with:duration:options:animations:completion:) | Apple Developer Documentation

Creates a transition animation for the specified container view.

developer.apple.com

 

transition(from:to:duration:options:completion:) | Apple Developer Documentation

Creates a transition animation between the specified views using the given parameters.

developer.apple.com

 

1️⃣ UIView의 transition 코드 살펴보기

오늘은 <나다 NADA> 개발에서 카드를 뒤집을 때 사용했던 애니메이션에 대해 설명해 보겠다.
코드는 UIView에 Animation을 적용하는 것이 전부이지만, View가 카드 형태로 되어있어 마치 카드가 뒤집히는 듯한 느낌을 주는 것이 핵심이다.

Trainsition animation을 구현하는 코드는 두 방식이 있다.

첫째는, 저장되어 있는 컨테이너 뷰를 뒤집는 (Creates a transition animation for the specified container view) 방식이다.

  • with view: transition 애니메이션이 적용되는 대상인 UIView
  • duration : 애니메이션의 지속시간
  • options : 어떤 애니메이션을 넣을지 (애니메이션의 종류는 자세하게 아래에서 설명해 보도록 하겠다!)
  • animations : 애니메이션 중에 적용하려는 변경 사항을 추가하는 부분 (클로저 코드 블럭으로 추가할 수 있다!)
  • completion : 애니메이션이 끝나고 실행할 부분 (클로저 코드 블럭으로 추가할 수 있다!)

transition Animation 적용 방법 01

둘째는 파라미터로 입력받은 두 뷰 사이의 전환 (Creates a transition animation between the specified views using the given parameters.)을 구현해 주는 방식이다.

  • from fromView : 전환을 시작할 때 UIView
  • to toView : 전환이 끝날 때 UIView -> fromView 위에 상위 뷰로 추가되는 방식이다.
  • duration : 애니메이션의 지속시간
  • options : 애니메이션 중에 적용하려는 변경 사항을 추가하는 부분 (클로저 코드 블럭으로 추가할 수 있다!)
  • completion : 애니메이션이 끝나고 실행할 부분 (클로저 코드 블럭으로 추가할 수 있다!)

transition Animation 적용 방법 02

 

2️⃣ transition 예제 코드

이론을 살펴봤으니 예시로 실제 사용하는 코드를 작성해 보겠다.

나는 예제 코드로 위의 두 방법 중 하나의 뷰를 사용하는 전자의 방법을 사용했고,
만약 뒤집었을 때 뷰가 크게 변하는 경우에는 후자의 방법을 사용해서 View 자체를 바꾸는 방식으로 구현하는 것이 좋을 것이다.

UIView의 앞면은 systemPink 컬러, 뒷면은 systemBlue 컬러로 지정했다.
Bool 타입의 isFront 변수 값에 따라 someView.backgroundColor 값이 바뀔 수 있게 삼항 연산자를 사용했다.

    private var isFront: Bool = true {
        didSet {
            someView.backgroundColor = isFront ? .systemPink : .systemBlue
        }
    }

실제 애니메이션 부분은 아래와 같이, 버튼을 눌렀을 때 위에서 배웠던 트랜지션 코드를 사용한 것을 볼 수 있다.
*만약 실제 프로젝트에서 사용한다고 하면, transition 부분을 별도의 Extension 메서드로 지정하면 짧게 사용할 수 있을 것으로 보인다!

    @objc
    func buttonTapped() {
        if isFront {
            isFront = false
            UIView.transition(with: someView,
                              duration: 0.5,
                              options: .transitionFlipFromLeft,
                              animations: nil,
                              completion: nil)
            
        } else {
            isFront = true
            UIView.transition(with: someView,
                              duration: 0.5,
                              options: .transitionFlipFromRight,
                              animations: nil,
                              completion: nil)
        }
    }

동작 화면!

전체 코드도 같이 첨부한다!

import UIKit
import SnapKit

class AnimationViewController: UIViewController {
    
    private var isFront: Bool = true {
        didSet {
            someView.backgroundColor = isFront ? .systemPink : .systemBlue
        }
    }
    
    private let someView: UIView = {
        let view = UIView()
        view.backgroundColor = .systemPink
        view.layer.cornerRadius = 10
        return view
    }()
    
    private let someButton: UIButton = {
        var config = UIButton.Configuration.borderedTinted()
        config.title = "애니메이션 시작"
        let button = UIButton(configuration: config)
        button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
        return button
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        self.view.backgroundColor = .white
        setupLayout()
    }
    
    func setupLayout() {
        [someView, someButton].forEach {
            self.view.addSubview($0)
        }
        
        someView.snp.makeConstraints {
            $0.centerX.equalToSuperview()
            $0.top.equalToSuperview().offset(150)
            $0.width.equalTo(200)
            $0.height.equalTo(300)
        }
        
        someButton.snp.makeConstraints {
            $0.top.equalTo(someView.snp.bottom).offset(100)
            $0.centerX.equalToSuperview()
        }
    }
    
    @objc
    func buttonTapped() {
        if isFront {
            isFront = false
            UIView.transition(with: someView,
                              duration: 0.5,
                              options: .transitionFlipFromLeft,
                              animations: nil,
                              completion: nil)
            
        } else {
            isFront = true
            UIView.transition(with: someView,
                              duration: 0.5,
                              options: .transitionFlipFromRight,
                              animations: nil,
                              completion: nil)
        }
    }
}

 

3️⃣ options 파라미터에 들어갈 다양한 Transition AnimationOptions 살펴보기

여러 가지 옵션을 바꿔봤을 때, 어떤 모습의 애니메이션이 동작하는지 살펴보도록 하겠다.

  • .transitionCrossDissolve : 뷰가 점진적으로 흐려지면서 사라지고, 다음 뷰는 서서히 나타나는 fade-out 효과
  • .transitionCurlUp : 뷰가 위쪽으로 말리는 듯한 효과
  • .transitionCurlDown : 뷰가 아래 방향으로 말리는 듯한 효과

왼쪽부터 순서대로 transitionCrossDissolve, transitionCurlUp, transitionCurlDown 옵션

이번에 아래 4가지 효과는 모두 카드가 뒤집히는 듯한 애니메이션을 보여주는 효과들이다.

  • .transitionFlipFromLeft : 뷰가 왼쪽에서 오른쪽으로 뒤집히며 전환되는 효과
  • .transitionFlipFromRight : 뷰가 오른쪽에서 왼쪽으로 뒤집히며 전환되는 효과
  • .transitionFlipFromTop : 뷰가 위쪽에서 아래쪽으로 뒤집히며 전환되는 효과
  • .transitionFlipFromBottom : 뷰가 아래쪽에서 위쪽으로 뒤집히며 전환되는 효과

왼쪽은 transitionFlipFromLeft/Right 적용, 오른쪽은 transitionFlipFromTop/Bottom 옵션