2017년 2월 15일 수요일

CALayer - CAShapeLayer

CAShapeLayer 는 Shape 를 그리기 위한 CALayer 기반 클래스이다. 다르게 말하면 다각형 혹은 폴리곤을 그리기 위한 용도라고 볼 수 있다.

사실 CAShapeLayer 자체는 그다지 어려울 건 없다. 사용하기 까다로운 것은 Shape 를 나타내는 데이터인 CGPath 를 만들어 내는 단계이다. 물론 다행히도 우리에겐 여러가지 도구가 제공되고 있긴 하다.

iOS 의 경우

아래 코드는 특정 뷰(UIView)에 삼각형을 그리는 CAShapeLayer 를 올리는 예제이다.
let view = UIView(frame: CGRect(x: 0, y: 0, width: 500, height: 500))

let path = UIBezierPath()
path.move(to: CGPoint(x: 250, y: 50))
path.addLine(to: CGPoint(x: 450, y: 450))
path.addLine(to: CGPoint(x: 50, y: 450))
path.close()

let layer = CAShapeLayer()
layer.frame = view.bounds
layer.path = path.cgPath
layer.fillColor = UIColor.red.cgColor
layer.strokeColor = UIColor.black.cgColor
layer.lineWidth = 10
view.layer.addSublayer(layer)
CAShapeLayer 의 path 라는 프로퍼티가 핵심이다. 이 path 는 CGPathRef 를 넣어야 하는데, 이를 위해서 UIBezierPath 라는 클래스를 이용한다. (베지어는 보통 곡선을 그리기 위한 알고리즘으로 알려져 있는데 직선 또한 포함되니 오해말자)
참고로 iOS 용 예제는 UIBezierPath 클래스를 이용해 구현했다. CGPath 를 직접 사용하는 예는 아래의 macOS 항목을 참고하자.
여기서 UIBezierPath 의 move(to:)를 이용해 시작점을 찍고, 이후 addLine(To:) 메소드를 이용해 시작점에서 부터 선을 이어 나간다. 그리고 마지막에 close() 메소드를 통해 연결된 선을 마무리한다. 마무리 시에는 끝점과 시작점이 자동으로 이어지는 것 같다. 그리하여 최종적으로 위와 같은 삼각형이 그려진다.

CAShapeLayer 의 fillColor 라던가 strokeColor 그리고 lineWidth 같은 프로퍼티들은 굳이 설명이 필요없을 것 같다.

이번에는 원을 그리는 예제를 보자.
let view = UIView(frame: CGRect(x: 0, y: 0, width: 500, height: 500))

let path = UIBezierPath(ovalIn: CGRect(x: 50, y: 50, width: 400, height: 400))

let layer = CAShapeLayer()
layer.frame = view.bounds
layer.path = path.cgPath
layer.fillColor = UIColor.red.cgColor
layer.strokeColor = UIColor.black.cgColor
layer.lineWidth = 10
view.layer.addSublayer(layer)
이번에는 path 를 만드는 방법이 생성자 init(ovalIn:) 만을 사용했다. 입력으로 들어간 CGRect 의 크기에 가득 차는 원을 그리게 된다.

UIBezierPath 클래스에는 이 외에도 몇 가지 도형을 간단히 그리기 위한 몇 가지 생성자가 있다. 예를 들어 init(roundedRect:cornerRedius:) 같이 모서리가 둥근 사각형 폴리곤을 생성하는 것도 있다.

이번에는 하나의 Path 인스턴스에 두 개의 도형을 겹쳐보자.
let view = UIView(frame: CGRect(x: 0, y: 0, width: 500, height: 500))

let path = UIBezierPath(ovalIn: CGRect(x: 100, y: 100, width: 300, height: 300))
path.move(to: CGPoint(x: 250, y: 50))
path.addLine(to: CGPoint(x: 450, y: 450))
path.addLine(to: CGPoint(x: 50, y: 450))
path.close()

let layer = CAShapeLayer()
layer.frame = view.bounds
layer.path = path.cgPath
layer.fillColor = UIColor.red.cgColor
layer.strokeColor = UIColor.black.cgColor
layer.lineWidth = 10
view.layer.addSublayer(layer)
폴리곤을 만들 때 이미 만들어진 폴리곤에 move(to:)를 이용해 새로운 시작점을 추가하는 것도 가능하다는 것을 알 수 있다.

그런데 이번에는 레이어에 옵션을 하나 추가해 볼까 한다. CAShapeLayer 의 프로퍼티 중 fillRule 의 값을 세팅하는 것인데 아래 코드 한 줄이다.
layer.fillRule = kCAFillRuleEvenOdd

이 한 줄이 들어가면 결과가 좀 바뀐다.
두 개의 폴리곤이 겹치는 영역이 비워지는 형태로 그려졌다. Even Odd 라는 말로 보아 홀수번 겹치는 영역만 칠하는 형태라고 해석 할 수 있을 것 같다. 대충 정리하자면 겹치는 영역은 그리지 않는 것이다.

kCAFillRuleEvenOdd 의 반대값은 kCAFillRuleNonZero 이고 이 값은 기본 설정이다. 즉 Non Zero 는 겹치는 거와 관계 없이 그린다.

macOS의 경우

macOS의 경우 UIBezierPath 에 대응하는 NSBezierPath 클래스가 제공된다. 그런데, 이 NSBezierPath 는 CGPath 타입 인스턴스를 돌려주는 기능이 없다. 그래서 CGPath 타입으로 변경하는 코드를 짜거나 혹은 CGPath API 를 이용해 직접 만들어야 된다.

원래대로라면 CGPath API 들은 CoreGraphics 의 C 함수들로 구성되어 있는데, 다행히도 현재의 Swift 3 에서는 CGPath 라는 이름의 클래스로 재구축 되어서 좀 더 쓰기가 쉬워진 편이다.

아래 코드는 앞에서 본 iOS 예제 제일 처음에 있는 삼각형 그리기 예제와 동일한 결과를 만들어낸다. 차이점은 path 를 생성하기 위해 CGPath 클래스를 이용한다는 점이다.
let view = NSView(frame: NSRect(x: 0, y: 0, width: 500, height: 500))
view.wantsLayer = true

let path = CGMutablePath()
path.move(to: CGPoint(x: 250, y: 450))
path.addLine(to: CGPoint(x: 450, y: 50))
path.addLine(to: CGPoint(x: 50, y: 50))
path.closeSubpath()

let layer = CAShapeLayer()
layer.frame = view.bounds
layer.path = path
layer.fillColor = NSColor.red.cgColor
layer.fillRule = kCAFillRuleEvenOdd
layer.strokeColor = NSColor.black.cgColor
layer.lineWidth = 10

view.layer?.addSublayer(layer)
CGPath 를 이용해 UIBezierPath 와 비슷하게 삼각형을 그리는 예제이다. CGMutablePath 라는 클래스는 이름 답게 Mutable 한 CGPath 타입이다. 즉 Path 에 점이나 선을 마음껏 추가하거나 삭제하는게 가능하다. (물론 수정 불가능한(immutable)건 그냥 CGPath 타입이다)

그런데 좌표계 위아래가 뒤집어졌다는 것에 주의하자. Cocoa 의 좌표계는 그래프 좌표계라 iOS 와 세로축(y-axis)이 반대이다.

위의 CGPath 를 활용하는 코드는 iOS 에서도 동일하게 쓸 수 있으리라 생각된다. 따라서 양쪽에 호환성 있는 코드를 사용해야 한다면 CGPath 를 이용하는 것도 고려해 보자.

이상 CAShapeLayer 에 대해 간략히 살펴봤다.

댓글 없음 :