2017년 2월 7일 화요일

CALayer 시작하기

지금까지 iOS 및 macOS 용 앱을 여럿 만들어 왔지만 레이어에 대해서는 자세히 공부해 본 적이 없었다. 블로그에 글 쓸 주제도 적어지고 마침 필요도 하기에 레이어에 대해 공부해 보면서 알게 된 것들을 정리해 보려고 한다.

iOS든 macOS든 모든 화면은 뷰(View) 단위로 구성된다. 최상위 윈도우가 있더라도 여기에 뷰가 있다. 각 뷰 컨트롤러에도 뷰가 있다. 모든 화면은 뷰가 쌓인 구조로 표시된다.

하나의 뷰는 뷰 그리기 루틴(drawRect), 서브뷰(Subviews)들과 함께 레이어(Layer)라는 부품들로 구성된다. 특히 레이어는 뷰에서 눈에 보여지는 부분을 다루는 중요한 개념이자 기능이다.

이 글은 레이어의 최상위 클래스인 CALayer 위주로 서술한다.

레이어의 특징

분류하긴 좀 애매하지만 개인적으로 레이어의 특징을 꼽아보자면 이렇다.
  • CA 즉 CoreAnimation 의 일부이다. 개념상으로 볼 때 OpenGL 과 View 사이에 속해있다.
  • 뷰(View)의 구성요소로써 주로 눈에 보이는 부분을 담당한다.
  • 뷰에는 하나의 레이어만 있을 수 있다. 대신 레이어는 서브레이어(Sublayers)를 여럿 가질 수 있다. 그리고 이름답게 다층(multi layer) 구조로 구성 할 수 있다.
  • 가볍다. 뷰를 여러개 쌓아서 어떤 모양을 만들었다면, 같은 모양을 레이어를 쌓아서 만드는게 훨신 가볍고 그래서 애니메이션에도 유리하다.
  • 레이어 프로퍼티를 바꾸면 의도치않게 애니메이션으로 바뀌는 모습이 보이기도 한다.
  • 뷰에는 없는 스타일에 관한 다양한 프로퍼티가 있다.
  • 레이어는 뷰의 draw(_ rect:)로 그려진 위에 표시된다. 서브뷰들은 이 레이어 위에 얹혀진다.

iOS 와 macOS 의 레이어(CALayer) 차이점

이 부분은 미리 언급해 두는게 좋을것 같다. 이 둘 사이에는 간단하지만 큰 차이가 존재한다.

iOS(Cocoa Touch 혹은 UIKit 기반) 에서 사용하는 UIView 의 경우 기본적으로 layer 라는 읽기 전용 프로퍼티가 존재한다. 즉 뷰를 생성하면 레이어도 같이 생성된다.

그런데 macOS(Cocoa 혹은 AppKit 기반) 에서 사용하는 NSView 의 경우 layer 프로퍼티는 읽기 쓰기가 되지만 옵셔널이다. 즉 없을 수도 있다. 실제로 NSView 기반 클래스의 인스턴스를 생성하면 기본적으로 layer 는 nil 이며, wantsLayer 라는 프로퍼티를 true 로 세팅하면 layer 에 기본 레이어 인스턴스가 생성된다.


UIView 와 NSView 는 좌표계의 차이가 있다. UIView 는 일반 좌표계(정식 이름은 잘 모르겠지만 좌측 상단이 0, 0)인 반면 NSView 는 기본적으로 그래프 좌표계(좌측 하단이 0, 0)다. 물론 뒤집는 것도 가능하다.

그런데 이 차이는 레이어에서도 동일하다. iOS 용 레이어는 일반 좌표계인 반면 macOS 용 레이어는 그래프 좌표계다. 이를 뒤집기 위해 isGeometryFlipped 라는 Bool 타입 프로퍼티가 제공되는데 이를 true 바꾸면 좌표계가 상하 뒤집어진다. 즉 그래프 좌표계를 일반 좌표계로 바꿀 수 있다. 물론 iOS의 경우에는 반대로 뒤집힌다는 점에 주의하자.
iOSView.layer.isGeometryFlipped = false    // 일반 좌표계
macOSView.layer?.isGeometryFlipped = false // 그래프 좌표계

iOSView.layer.isGeometryFlipped = true     // 그래프 좌표계
macOSView.layer?.isGeometryFlipped = true  // 일반 좌표계
약간의 차이가 더 있겠지만 대부분의 영역에서 둘 다 레이어에 관한건 비슷하다.

사용해보기

레이어 사용 방법은 뷰 프로그래밍(View Programming)을 하는 것과 비슷하다. 앞서 이야기 한 대로 기본적으로 뷰는 레이어를 하나 가지고 있으므로 거기서 출발 할 수 있다. 서브레이어를 만들던가 등등 말이다.
참고로 이 예제들은 iOS Playground 에서 작성해서 테스트 해 본 것이다. macOS 와의 차이점이 있을 경우 해당 항목에 기록해 놓았다.
우선은 뷰를 하나 만들어서 기본 레이어의 배경 색을 바꿔보자. 여기서는 까만 색으로 칠해본다.
let view = UIView(frame: CGRect(x: 0, y: 0, width: 256, height: 256))
view.layer.backgroundColor = UIColor.black.cgColor
UIView 의 backgroundColor 프로퍼티를 이용할 수도 있겠지만 레이어를 이용하는 것도 사실 다를 건 없다. :-)
macOS 의 NSView 의 경우 앞서 이야기 했다싶이 레이어를 사용하기 전에 wantsLayer 프로퍼티를 true 로 바꿔서 기본 레이어가 생성되도록 해야한다는 점을 기억하자.
이제 여기에 약간 작은 흰색 레이어를 추가해 보자. 배경 레이어보다 작게 프레임(frame)을 설정해주고 addSublayer() 를 이용해 서브레이어로 추가할 수 있다.
let layer = CALayer()
layer.frame = CGRect(x: 10, y: 10, width: 236, height: 236)
layer.backgroundColor = UIColor.white.cgColor
view.layer.addSublayer(layer)

뭔가 딱딱한 것 같으니 둥글둥글하게 바꿔보자. cornerRadius 값을 주면 외곽 부분을 둥글게 만들 수 있다.
layer.cornerRadius = 40

새로 추가한 레이어에 이미지를 올려볼까? contents 프로퍼티에는 표시할 이미지를 지정 할 수 있다.
let image = UIImage(named: "apple.png")
layer.contents = image?.cgImage
contents 프로퍼티는 CGImageRef 값을 주로 받지만, macOS 의 경우 NSImage 인스턴스도 받을 수 있다. NSImage 에는 cgImage 같은 프로퍼티가 없다는 점을 생각해 보면 참 다행이다.
그런데 이미지를 넣으니 기껏 둥글게 해 놓은 것이 사라졌다.

사실은 사라진건 아니고 둥근 구석 위에 이미지가 덮어서 안보이는 것이다. 이를 위해 이미지가 레이어 영역 외에는 표시되지 않도록 마스크를 만들어 줄 필요가 있다. masksToBounds 값을 true 로 세팅하면 레이어 영역을 마스크로 만들어 준다.
layer.masksToBounds = true
아이폰 스프링보드의 앱 아이콘 같이 보인다. :-)

이제 이 위에 또다른 레이어를 하나 추가해 보려고 한다. 작고 귀여운 스위프트 새(?)를 올려보자.
let swiftLayer = CALayer()
swiftLayer.frame = CGRect(x: 20, y: 20, width: 64, height: 64)
swiftLayer.backgroundColor = UIColor.white.cgColor
swiftLayer.contents = UIImage(named: "swift.png")?.cgImage
layer.addSublayer(swiftLayer)

젠장 작고 귀여울 줄 알았는데 흰색 배경 때문에 거슬린다. 땜빵으로 그림자라도 넣어볼까?
swiftLayer.shadowOffset = CGSize(width: 5, height: 5)
swiftLayer.shadowRadius = 5
swiftLayer.shadowOpacity = 0.8

음... 아무래도 이쁘진 않다. 뭐 급조한 예제니 그러려니 하자. :-)

이외에도 엄청난 수의 프로퍼티들이 제공된다. 예를 들어 위의 이미지를 표시하는 경우
  • contentsGravity: 이미지 확대 축소 시 배율. 예) kCAGravityResizeAspect
  • minificationFilter: 이미지 축소 시 보정(?) 예) kCAFilterTrilinear
  • contentsScale: 배율 표기인데 주로 레티나 여부 판단용으로 쓰인다. 예) UIScreen.main.scale
등의 프로퍼티도 살펴보자. 물론 위의 예시도 극히 일부에 불과해서 지식이 없다면 좀 방황할지도 모르겠다. XD

이런 식으로 마치 서브뷰(?)를 쓰듯이 레이어를 이용해 하나의 뷰를 다채롭게 꾸밀 수 있다.

마무리

원래 이 글은 소개 수준으로 간단하게 넘어가려고 했었는데 어떻게 쓰다보니 길어졌다. 그런데 길어졌다고 하기엔 내용이 너무 많아서 아주 극소수의 부분만 적은 것에 불과하다.

CALayer 는 최상위 레이어 클래스다. 이를 상속받아서 각종 특수한 전문화된 레이어 기능들도 있다. 너무 많아서 소개하기 힘들 정도다. 어떤 레이어들이 있는지는 애플의 Quartzcore 공식 문서(하단 관련링크 참조)를 보는 것이 좋을 것 같다. 심볼 중 Layer 로 끝나는 이름들이 모두 미리 구현되어 있는 레이어들이다.
그리고 레이어와 관련된 기능들도 많다. 예를 들자면 애니메이션이다.
아직 공부할 내용들이 많은 것 같다. 관련된 글이 추가되면 위 세부글 항목과 아래에 관련글 링크로 남길 예정이다.

[관련링크] Core Animation Programming Guide
[관련링크] Quartzcore Framework
[관련링크] CALayer Class

[관련글] 스위프트(Swift) 가이드

댓글 없음 :