2017년 4월 12일 수요일

CALayer 와 CAAction

Implicit Animation

이전에 CALayer 애니메이션에서 언급한 방법들은 Explicit Animation 즉 명시적인 애니메이션이라고 불린다. 이와 반대 개념으로 암시적인(Implicit) 애니메이션도 있을 터인데 이 암시적 애니메이션은 레이어의 동작(Behavior)에 의해 발동되는 애니메이션이다. 쉽게 말해서 프로퍼티 등에 값을 넣으면 애니메이션이 발동하면서 변화되는 모습을 보여준다. 프로그래머가 직접 애니메이션을 명령하지 않았지만 자동으로 애니메이션이 동작하였기에 암시적이라고 부르는 것이다.
view.layer.contents = UIImage(named: "flower").cgImage
위 코드는 뷰 레이어에 이미지를 표시하는 기초적인 코드이다. 그런데 이 코드 이전에 이미 contents 에 이미지가 올라가 있는 상태라면 다른 이미지로 바로 바뀌지 않고 페이드 애니메이션, 즉 이미지가 서서히 나타나는 듯한 효과로 변경(transition)된다. 물로 OS나 기타 조건에 의해 바뀔 수는 있으니 절대적인 것은 아니다. 중요한 점은 그저 레이어의 프로퍼티를 바꿨는데 애니메이션이 발동된다는 것이다. 이게 바로 암시적 애니메이션이다.

이 글에서는 이 암시적 애니메이션에 관해 다룬다. 암시적 애니메이션을 표현하기 위해서는 CAAction 이라는 프로토콜에 대해 알고 지나가야 할 것 같다.

CAAction Protocol

이 프로토콜의 매뉴얼을 보면 알 수 있지만 run 이라는 불리우는 이름의 메소드 달랑 하나만이 정의되어 있다. 말 그대로 특정한 애니메이션을 실행시키기 위한 프로토콜이다.

CAAction 프로토콜을 구현한 대표적인 클래스는 CAAnimation이다. 즉 CALayer 애니메이션 글에서 살펴본 CABasicAnimation 이나 CAKeyframeAnimation 등등은 이미 CAAction 을 따르도록 설계되었다.

따라서 CAAction 프로토콜을 준수하는 오브젝트를 요구하는 경우에는 이들 애니메이션 클래스의 인스턴스를 적당히 만들어서 넘겨주면 된다.

CALayer 에서 액션과 관련된 것들

CALayer 는 특정 키의 변화(behavior)가 감지되면 action(forKey event: String) 메소드를 이용해 키에 해당하는 액션을 찾는다. 이 메소드는 아래와 같은 순서로 액션을 찾아본다.
  1. delegate: CALayerDelegate 의 action(for:forKey) 라는 것을 구현
  2. actions: [String : CAAction] 타입의 데이터
  3. style
  4. defaultAction(forKey:)
여기서 실생활(?)에 유용한 것은 아마도 1번과 2번 항목일 것이다. 특히 2번이 아마 가장 손쉬운 방법이 아닐까 생각된다.

Change Action

간단한 예제를 하나 보자. contents 가 바뀔 경우 rotate 트랜지션 애니메이션이 나타나도록 설정하는 코드이다.
let transition = CATransition()
transition.duration = 0.5
transition.type = "rotate"
layer.actions = ["contents" : transition]
위 코드는 특정 레이어에 contents 프로퍼티가 변경되면 transition 으로 만들어 둔 애니메이션이 동작하도록 한다. 여기서 "contents" 라는 키(key)는 CALayer Animation 에서 전반적으로 사용하는 그 키와 동일하다.

위의 예제는 앞서 action 을 찾는 순서에서 2번에 해당하는 항목이다. 그렇다면 1번에 해당하는 방법도 있을텐데 이건 생략하도록 한다. 이미 방법은 다 알려줬고 그냥 delegate 스타일로 구현하면 되니까. ;-)

Disable Action

애플 공식 문서에서 액션을 임시로 무력화(?) 시키기 위한 방법으로 아래와 같은 식의 코드를 소개하고 있다.
[CATransaction begin];
[CATransaction setValue:(id)kCFBooleanTrue
                 forKey:kCATransactionDisableActions];

// 레이어 컨텐츠를 바꾼다.
layer.contents = someImage;

[CATransaction commit];
위의 예제는 Objective-C 코드인데 Swift 버전으로 바꾸면 아래와 같다.
CATransaction.begin()
CATransaction.setValue(true, forKey: kCATransactionDisableActions)

// 레이어 컨텐츠를 바꾼다.
layer.contents = someImage

CATransaction.commit()
이런 식으로 임시로 액션을 무력화(?) 시킬 수 있다.

하지만 임시가 아니라면 어떻게 할까? 앞서 우리는 actions 라는 프로퍼티로 액션을 정의해 줄 수 있었는데 이를 이용해 아예 액션을 없애버리는 것이 가능하다.
// disable action for "contents" key
layer.actions = ["contents" : NSNull()]
nil 이 아니라 NSNull 이 쓰였다는 점에 주의하자. 이 NSNull 은 nil 값 자체를 쓸 수 없는 곳에서 null 값을 표현하기 위해 사용되는 특수 클래스이다. 해당 키에 대한 액션이 NSNull 로 설정되면 액션을 사용하지 않는 것으로 설정된다.
왜 nil 을 못 쓰는가는 일단 actions 에 넣을 수 있는 타입 자체가 옵셔널이 아닌 것도 한 원인이 되겠지만, Objective-C 에서 사전형(NSDictionary)에 nil 이라는 값(Value)을 넣을 수 없다라는 것도 한 이유 중 하나이다. 그 외에 actions 는 아니지만, nil 을 리턴하면 기본 액션(default action)을 의미하는 경우도 있다.

마무리

사실 이 글은 위의 액션 무력화(?)에 대한 방법을 찾다가 우연찮게 공부하게 된 내용이다. 무력화를 제외하고 얼마나 유용할지는 잘 모르겠는데 왠지 잘 쓰면 아주 좋은 효과가 날지도 모른다는 생각이 든다.

[관련글] CALayer 시작하기
[관련글] CALayer Animation (Core Animation)
[관련글] CATransition Animation (Core Animation)
[관련글] 스위프트(Swift) 가이드

댓글 없음 :