본문 바로가기

iOS

[CoreAnimation] 애니메이션 구현 - CoreBasicAnimation

오늘은 iOS나 OS X에서 애니메이션을 구현하는 방법 중 하나인 Core Animation을 사용해보도록 하겠습니다~ 

CoreAnimation의 가장 추상화된 클래스는 CAAnimation입니다!

 

CAAnimation 클래스는 직접 선언하여서 사용하는 것이 아닙니다. CAAnimation을 이용하려면 이를 상속받은 클래스들인 CABasicAnimation, CAKeyFrameAnimation, CAAnimationGroup, CATransition을 선언해야 합니당 

오늘은 이들 중에서 CABasicAnimation을 사용해보도록 할게요!

 

CABasicAnimation이란?

먼저 애플문서에서 정의를 알아볼까요?

 

여기서 잠깐,,!!! CAPropertyAnimation가 나오는데 이게 뭘까요... 애플문서 헤집고 다닌 결과,, 

CAPropertyAnimation은 CAAnimation밑에 있고 CAKeyFrameAnimation과 CABasicAnimation을 가지고 있어요!

즉, NSObject > CAAnimation > CAPropertyAnimation > CABasicAnimation, CAKeyFrameAnimation 단계로 추상화됩니다.

 

그럼 이제 드뎌 정의를 볼게요!

An object that provides basic, single-keyframe animation capabilities for a layer property.

layer 속성을 위한 하나의 keyframe 애니메이션을 제공해주는 객체라고 합니다.

이 뜻을 해석하기 전에 알아야 할 것은 CAAnimation은 layer을 이용한 애니메이션입니다. Core Animation Programming Guide 의 말을 빌리자면 이는 view의 UI표시영역만 사용하게 되어서 main thread의 부담을 덜어주게 됩니다.  view를 소프트웨어적으로 다시 그리지 않고 UI 표시영역은 그 부위의 비트맵 상의 이미지를 캡처하여 이를 그래픽적으로 쉽게 조작할 수 있도록 도와줍니다. 

더 자세한 부분은 위 링크에서 알아 볼 수 있어요!

 

간단히 정리하면!

CABasicAnimation은 layer 속성을 이용해서 하나의 애니메이션을 구현할 수 있게 도와주는 객체라는 의미로 해석되네요~!

위에 CAKeyFrameAnimation도 있는데(다음에 알아볼 것)  이것은 여러개의 애니메이션을 묶어서 구현할 수 있게 도와주는 객체라는 것을 유추해볼 수 있겠네요!!! 맞겠죠?

 

CABasicAnimation의 속성들 - fromValue, toValue, byValue

본론으로 돌아와서 CABasicAnimation은 keypath를 이용해 property의 값을 변경하는 애니메이션을 만들어요. 아래는 opacity property를 변경하는 애니메이션이에요.

let animation = CABasicAnimation(keyPath: "opacity") 
animation.fromValue = 0 
animation.toValue = 1

property의 값을 변경하기 위해서 fromValue, toValue, byValue 세자기 속성들을 설정하게 되는데 이 상관관계를 알아야합니다!

모두 옵셔널로 되어 있어 아무 설정도 안하면 해당 property의 직전 값에서 현재값으로 변하게 됩니다. 

 

1. fromValue, toValue 사용 : 해당 property가 from에서 to로 변합니다.

2. fromValue, byValue 사용 : 해당 property가 from에서 (from+by)로 변합니다.

3. byValue, toValue 사용 : 해당 property가 (toValue - byValue)에서 toValue로 변합니다.

4. fromValue 사용 

5. toValue 사용 

6. byValue 사용

 

그냥 해석되는대로 쓰시면 될 것 같아요! from은 어디서부터, to는 어디까지, by는 얼만큼 변하나!

자세한 내용은 요기 참고!

코드 구현

그럼 한 번 직접 구현해보도록 할게요!

 

아래와 같이 뷰와 버튼을 만들어주고, autolayout까지 잡아주었습니다.

 
    let animateView: UIView = {
        let view = UIView()
        view.backgroundColor = .blue
        view.layer.cornerRadius = view.frame.width/2
        view.translatesAutoresizingMaskIntoConstraints = false
        return view
    }()
    
    let button: UIButton = {
        let button = UIButton()
        button.layer.borderWidth = 1
        button.layer.borderColor = UIColor.black.cgColor
        button.layer.cornerRadius = 20
        button.setTitle("애니메이션 실행", for: .normal)
        button.backgroundColor = .red
        
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        view.addSubview(button)
        button.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -100).isActive = true
        button.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
        button.widthAnchor.constraint(equalToConstant: 150).isActive = true
        button.heightAnchor.constraint(equalToConstant: 60).isActive = true
        button.addTarget(self, action: #selector(pressB), for: .touchUpInside)
        
        view.addSubview(animateView)
        animateView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
        animateView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
        animateView.widthAnchor.constraint(equalToConstant: 50).isActive = true
        animateView.heightAnchor.constraint(equalToConstant: 50).isActive = true
        animateView.clipsToBounds = false
        animateView.layer.masksToBounds = true
    }

그 후, 이제 애니메이션을 만들어볼 건데요!! 아래와 같이 만들어보았습니다.

@objc func pressB() {
    self.animation(with: self.animateView, completion: nil)
}
    
func animation(with viewToAnimate: UIView, completion: (()->Void)?) {
    let expandAnimation = CABasicAnimation(keyPath: "transform.scale")
    let expandScale = (UIScreen.main.bounds.size.height/viewToAnimate.frame.size.height)*2
    expandAnimation.fromValue = 1.0
    expandAnimation.toValue = expandScale 
    expandAnimation.timingFunction = CAMediaTimingFunction(controlPoints: 0.95, 0.02, 1, 0.05)
    expandAnimation.duration = 0.4
    expandAnimation.fillMode = .forwards
    expandAnimation.isRemovedOnCompletion = false
    
    viewToAnimate.layer.add(expandAnimation,forKey: expandAnimation.keyPath)
}

코드 설명을 해보도록 할게요.

let expandAnimation = CABasicAnimation(keyPath: "transform.scale")
let expandScale = (UIScreen.main.bounds.size.height/viewToAnimate.frame.size.height)*2
expandAnimation.fromValue = 1.0
expandAnimation.toValue = expandScale

expandAnimation이라는 규모! 여기선 배로 변하는 크기 property를 변하게 하는 CABasicAnimation을 만들어줍니다.

그리고, 저는 화면을 꽉 채울 거여서 expandScale을 기기높이를 view의 높이로 나눈 몫에 2배를 해주었습니다.(2배를 해준 이유는 확실히 꽉 채우기위해서입니당)

from은 현재 값(사실 scale property이므로 설정안해줘도 됩니다.) 그리고 to는 변할 property 값으로 설정해주었습니다. 

expandAnimation.timingFunction = CAMediaTimingFunction(controlPoints: 0.95, 0.02, 1, 0.05)
expandAnimation.duration = 0.4
expandAnimation.fillMode = .forwards
expandAnimation.isRemovedOnCompletion = false
    
viewToAnimate.layer.add(expandAnimation,forKey: expandAnimation.keyPath)

이건 위에서 설명하지 않은 property들인데요! isRemovedOnCompletion과 timingFunction은 모두 CAAnimation의 property이고, duration과 fillMode는 CAMediaTiming 프로토콜 내의 property입니다. 이를 CAAnimation이 채택하고 있습니다.

 

간단히 설명하면 timingFunction은 애니메이션이 실행되는 타이밍을 곡선으로 표현해줍니다. [0, 0]  -> [0.95, 0.02] -> [1, 0.05] ->

[1, 1] 이런식으로요!

duration은 알다시피 애니메이션이실행되는 시간이고 fillMode는 애니메이션이 끝난 후, 마지막 보여지는 그 상태를 그대로 남길 것인가, 되돌릴 것인가의 의미입니다. 이건 forward로 설정해줘서 남아있게 해줬습니다.

마지막으로 isRemovedOnCompletion은 애니메이션이 끝났을 때, 이 애니메이션을 rendertree에서 제거할 것인가에 대한 bool값입니다. 저는 남아있게 해주고 싶어 false로 설정하였습니다.

그리고, layer에 이 애니메이션을 추가해주었더니 아래와 같이 실행되네영

CATransaction을 이용해서 애니메이션이 2초 후 종료하게 할 수 있습니다. 위의 fillMode와 isRemovedCompletion을 이용하지 않는 이유는 바로 종료가 되기 때문입니다.

CATransaction.setCompletionBlock {
    completion?()
    DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
        viewToAnimate.layer.removeAllAnimations()
    }
}
viewToAnimate.layer.add(expandAnimation,forKey: expandAnimation.keyPath)
CATransaction.commit()

setCompletionBlock을 이용하고, 마지막에 commit을 하여 적용시켜주면 되네영 CATransaction도 자세히 알아봐야하는데... 정말 공부할 것들이 계속 나오네요 ㅎㅎ....!!

 

읽어주셔서 감사합니다~!