OSX에서 애니메이션 구현 방법을 선택하는 방

OSX에서 창이나 뷰에 간단, 혹은 복잡한 애니메이션을 구현하는 방법은 다양한 층위에서 선택해야 한다. 다음은 Cocoa에서애니메이션을 적용하기 위해 어떤 수단을 선택해야 할지에 대한 가이드 내용이다. 참고로 이 가이드의 원문은 제법 오래전의 것으로 소소하게 업데이트하였다.

1. 뷰가 회전하지 않고, 주로 정적이며 커스터마이징 되지 않는 기본 코코아 컨트롤 위주로 구성되어 있다면 NSView(혹은 그 서브클래스)를 그대로 사용한다.

2. 이동이나 크기 변환과 같은 단순한 애니메이션만 적용하면 된다면 애니메이터 프록시를 사용하여 변환하면 된다. NSView는 기본적으로 NSAnimatablePropertyContainer 프로토콜을 따르고 있기 때문에 animator()를 호출하여 프록시를 얻고, 프록시를 통해 설정값을 변경하면 그 변경이 애니메이션으로 표현된다.

NSAnimatablePropertyContainer의 설명

https://developer.apple.com/documentation/appkit/nsanimatablepropertycontainer

NSAnimatablePropertyContainer 프로토콜을 따르는 객체는 animator 접근자를 통해 프로퍼티 값에 대한 암묵적인 애니메이션을 구동할 수 있는 프록시 객체를 리턴합니다. 객체의 애니메니터 프록시는 마치 그 자체가 원래의 객체인것처럼 다룰 수 있으며, 원래 객체를 인자로 받는 API에 전달될 수도 있습니다. 프록시에 대해서 “set”에 호환되는 키밸류 코딩 API를 호출하는 것은 타깃 객체에 대한 속성을 변경하고, 이를 애니메이션으로 표현하게 합니다.

객체에서 자동으로 애니메이트되는 속성들에 대해 NSAnimatablePropertyContainer는 자동으로 이를 감지하고 CAAnimation 객체를 리턴합니다. 변경 애니메이트 되는 과정에 있는 프로퍼티에 대해서 새로운 값을 지정하는 행위도 원칙적으로 유효하며, 이 경우 현재 값에 상관없이 새로운 타깃 값이 지정됩니다. 진행 중인 프로퍼티 애니메이션은 NSAnimationContext의 duration값을 0.0으로 설정함으로써, 중지할 수 있습니다.

3. 뷰가 회전하는 상태로 사용자 이벤트에 반응하거나, 콘텐츠를 계층화하여 그려야 한다면 레이어백 코코아 뷰(Layer-Backed View)로 만들고 코어애니메이션 레이어를 사용하라. 레이어 백 코코아뷰는 간단히 wantsLayer 속성을 true로 주면 된다. 코어 애니메이션 레이어를 획득할 수 있다면, 레이어의 기본적인 기하학적 속성을 변경하는 것으로 암시적인 애니메이션 효과를 만들 수 있다.

4. 커스텀뷰가 프로세서를 많이 사용하거나 지연이 발생한다면 레이어-백 뷰를 사용할 것을 고려해 본다. 레이어를 사용하는 커스텀 뷰는 그려진 내용을 비트맵으로 캐싱하기 때문에 성능이 우수하다.

5. 커스텀뷰가 다양한 사용자 이벤트를 받아야 한다면 레이어 백 코코아 뷰를 사용할 것을 고려해본다.

6. 아쿠아 컨트롤에 의존하지 않는 커스텀 UI를 사용한다면 CA레이어를 직접 제어하는 것을 고려해 본다. CALayer를 직접 제어하려면 레이어-백 뷰가 아닌 레이어 호스팅 뷰를 사용해야 한다. (레이어 호스팅 뷰를 사용하면 사용자 이벤트는 뷰에 의존하며, 시각 콘텐츠 관리는 코어애니메이션 인터페이스를 사용하게 된다.)

NSView의 wantsLayer 옵션에 관하여

https://developer.apple.com/documentation/appkit/nsview/1483695-wantslayer

이 프로퍼티를 true로 설정하면 뷰를 레이어-백 뷰로 전환합니다. 레이어-백뷰는 렌더링되는 콘텐츠를 관리하기 위해 CALayer를 사용합니다. 레이어 백 뷰를 만들면 이는 암묵적으로 해당 뷰의 모든 하위 계층에 포함되는 뷰를 레이어 백 뷰로 만듭니다. 따라서 모든 서브뷰와 그 자식뷰들은 레이어 기반이 됩니다. 이 속성의 기본값은 false입니다.

레이어 백 뷰에서 모든 그리기 동작의 산물은 내부의 레이어에 의해 캐시됩니다. 캐시된 콘텐츠를 조작하는 것은 명시적으로 콘텐츠를 다시 그리는 것에 비해 훨씬 더 좋은 퍼포먼스를 냅니다. 앱킷은 자동으로 내장 레이어 객체를 생성하며(이 때 makeBackingLayer()를 호출합니다.) 뷰 콘텐츠의 캐싱을 관리합니다. 만약 wantsUpdateLayer 메소드가 false를 리턴하면, 내장 레이어 객체와 직접 상호작용하지 않아야 합니다. 대신 뷰와 레이어에 어떤 변경을 적용하기 위해서 이 클래스의 메소드만을 사용해야 합니다. wantsUpdateLayer값이 true이면 뷰의 updateLayer() 메소드를 오버라이드하여 레이어를 수정하는 것이 허용됩니다.

레이어 백 뷰에 대해서 canDrawSubviewsIntoLayer 값을 true로 주어 레이어 계층 구조를 레이어 한 장에 압축하는 것이 허용됩니다. 서브 뷰의 내용이 뷰 레이어에 눌러붙어 버리는 것을 방지하기 위해서는 서브 뷰의 wantsLayer 옵션을 true로 변경해주세요.

레이버 백 뷰를 생성하는 것과 별개로 레이어 객체를 뷰의 layer 프로퍼티에 할당함으로 써 레이어-호스팅 뷰를 만들 수 있습니다. 레이어 호스팅 뷰에서는 레이어 객체를 관리하는 책임은 앱 킷이 아닌 개발자에게 돌아갑니다. 레이어 호스팅 뷰를 만들기 위해 레이어 프로퍼티를 먼저 할당한 후 wantsLayer 값을 true로 설정합니다. 이 설정의 순서가 매우 중요합니다.

또 레이어 호스팅 뷰에서 주의할 것은 그리기 작업을 뷰에 의존해서는 안된다는 것입니다. 레이어 호스팅 뷰에는 서브 뷰를 추가하지 마십시오. 레이어 호스팅 뷰에서 설정한 레이어는 레이어 계층 구조의 루트가 됩니다. 레이어 트리를 조작하는 모든 동작은 코어 애니메이션 인터페이스를 통해 이루어져야 합니다. 키보드나 마우스 이벤트를 다룰 때에는 뷰를 사용할 수 있지만, 모든 그리기 동작은 코어 애니메이션을 통해 수행되어야 합니다.