티스토리 뷰
[iOS] Core Animation Programming Guide (2) Setting Up Layer Objects
Hani_Levenshtein 2022. 2. 27. 13:00안녕하세요 Hani입니다.
Core Animation을 알아볼 거예욥.
목차는 이렇구
2. Setting Up Layer Objects
3. Animating Layer Content
4. Building a Layer Hierarchy
5. Advanced Animation Tricks
6. Changing a Layer’s Default Behavior
7. Improving Animation Performance
이번엔 두 번째 주제인 Setting Up Layer Objects 차례 ☺️
지난 포스팅에서는 Layer가 컨텐츠와 컨텐츠를 보여주기 위한 프로퍼티를 관리해준다는 것을 알아봤어요.
이번에는 Layer를 어떻게 구성하고 조작하는지 알아볼 건데
macOS에 대한 내용은 거의 생략할 예정입니다. 🙏
Enabling Core Animation Support in Your App
iOS에서는 Core Animation이 항상 활성화되어 있고 모든 뷰가 레이어를 가지고 있는데 macOS에서는 그렇지 않습니다.
macOS에서는 QuartzCore 프레임워크를 연결(link against)하고 NSView 객체에서 레이어를 활성화해야 합니다.
레이어를 활성화하기 위해선 코드로 NSView 객체의 wantsLayer 프로퍼티를 true로 두거나
nib 파일의 View Effects Inspector에서 레이어 지원을 활성화하면 됩니다.
Changing the Layer Object Associated with a View
Layer-backed View에서는 Root Layer가 기본적으로 CALayer지만 다른 Layer로 변경할 수 있습니다.
Changing the Layer Class Used by UIView
layerClass는 UIView의 타입 프로퍼티로,
CALayer를 상속받는 다른 레이어를 Root Layer로 사용하고 싶을 때 override하기만 하면 됩니다.
뷰가 생성되는 시점에 결정되고, 생성된 후에는 바꿀 수 없습니다.
Changing the Layer Class Used By NSView
- macOS
override makeBackingLayer() method
Layer Hosting Lets You Change the Layer Object in OS X
- macOS
Different Layer Classes Provide Specialized Behaviors
CALayer는 모든 레이어들의 부모 클래스로, 각 용도에 맞는 CALayer의 서브클래스들이 존재합니다.
모든 서브클래스의 용도를 설명하기엔 너무 길어서 표만 캡쳐해오겠습니다. 🥺
Providing a Layer’s Contents
Layer는 시각 컨텐츠를 비트맵으로 관리하는 객체입니다.
레이어에 컨텐츠를 할당하기 위해서는 크게 세 가지 방식이 있는데 하나씩 알아볼게욥.
1. Using an Image for the Layer’s Content
Layer는 단지 비트맵 이미지를 관리하는 컨테이너기 때문에
Layer의 contents 프로퍼티에 직접 이미지를 할당할 수 있습니다.
Layer가 할당받은 이미지를 복사하거나 때문에 같은 이미지를 여러 곳에서 사용하는 경우에 메모리를 아낄 수 있습니다.
컨텐츠가 변하지 않는다면 가장 좋은 방법이에요.
2. Using a Delegate to Provide the Layer’s Content
레이어의 컨텐츠가 동적으로 바뀐다면 CALayerDelegate를 통해 업데이트할 수 있습니다.
Delegate가 displayLayer 메서드를 구현한다면
구현이 비트맵을 생성하고 contents 프로퍼티에 할당하게 됩니다.
Delegate가 drawLayer 메서드를 구현한다면
Core Animation이 비트맵을 생성하고,
비트맵에 draw할 그래픽 컨텍스트를 생성한 다음
비트맵을 채우기 위해 delegate 메서드를 호출합니다.
Delegate는 두 메서드 중 하나를 반드시 구현해야 합니다.
만약 둘 다 구현한다면 레이어는 displayLayer 메서드만 호출하게 됩니다.
미리 렌더링 된 이미지가 있거나 비트맵을 생성해줄 객체가 있다면 displayLayer 메서드를 재정의 하는 것이 낫고
그렇지 않다면 drawLayer 메서드를 Override 하는 것이 좋습니다.
3. Providing Layer Content Through Subclassing
일반적인 방법은 아니지만 레이어를 서브클래싱할 수도 있습니다.
서브클래싱할 땐 레이어의 컨텐츠를 그리기 위한 두 가지 방법이 있는데
하나는 레이어의 display 메서드를 재정의하여 레이어의 contents 프로퍼티에 직접적으로 컨텐츠를 지정하는 것이고
다른 하나는 레이어의 draw 메서드를 재정의하여 그래픽 컨텍스트에 그리는 방법입니다.
Tweaking the Content You Provide
레이어의 contents 프로퍼티에 이미지를 할당할 때,
레이어의 contentsGravity 프로퍼티는 어떻게 이미지가 bounds에 맞게 조작될지 결정합니다.
기본적으로 이미지는 bounds보다 크거나 작은데,
만약 레이어 bounds의 aspect ratio(종횡비)가 이미지의 aspect ratio와 다르다면 이미지가 왜곡될 수 있습니다.
contentsGravity 프로퍼티는 크게 두 가지 값을 할당할 수 있습니다.
Position-based gravity는 크기를 조정하지 않은 이미지를 레이어 bounds의 특정 위치에 고정할 수 있습니다.
이미지가 bounds보다 작으면 배경색이 보이게 되고,
이미지가 bounds보다 크면 이미지가 잘려 보이게 됩니다.
Scaling-based gravity는 이미지를 옵션에 따라 왜곡시킬 수 있습니다.
contentsGravity는 위 사진의 가운데에 위치한 resize 옵션을 기본값으로 갖기 때문에
아무런 설정을 하지 않으면 컨텐츠가 레이어 bounds에 딱 맞게 크기가 조정됩니다.
Working with High-Resolution Images
레이어는 디바이스 화면의 해상도에 대한 아무런 정보도 가지고 있지 않습니다.
단지 비트맵 이미지에 대한 포인터를 저장해뒀다가 픽셀에 가장 최선의 방법으로 보여줄 뿐입니다.
contents 프로퍼티에 이미지를 할당하고 나서
해상도에 대한 정보를 레이어의 contentsScale 프로퍼티에 적절한 값을 설정한 뒤,
contentsScale + contents를 Core Animation에게 전달해줘야 합니다.
contentsScale는 기본값으로 일반적인 해상도에 맞는 1.0을 가지고 있으며,
레티나 디스플레이인 경우에는 2.0을 설정해야 합니다.
단, contentsScale에 직접 값을 할당하는 경우는 오직 레이어에 비트맵 이미지를 직접 할당할 때뿐입니다.
UIKit과 AppKit은 layer-backed view의 경우 컨텐츠와 화면 해상도에 기반하여
layer의 contentsScale에 자동적으로 적절한 값을 할당해줍니다.
Adjusting a Layer’s Visual Style and Appearance
레이어의 컨텐츠를 보충할 수 있는 테두리나 배경색 등의 옵션을 알아보겠습니다.
Layers Have Their Own Background and Border
Layer는 컨텐츠에 배경색과 경계선을 추가로 보여줄 수 있습니다.
배경색은 컨텐츠의 뒤에 렌더링 되며 컨텐츠의 투명한 부분을 통해 볼 수 있습니다.
경계선은 컨텐츠의 앞에 렌더링되며 Sublayer가 있다면 경계선 아래에 나타납니다.
검증 드가자
final class ViewController: UIViewController {
override func loadView() {
view = CoreAnimationPracticeView()
}
}
final class CoreAnimationPracticeView: UIView {
private var redlayer = CALayer()
private var bluelayer = CALayer()
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .brown
layer.addSublayer(redlayer)
redlayer.addSublayer(bluelayer)
redlayer.frame = CGRect(x: 100, y: 100, width: 100, height: 100)
bluelayer.frame = CGRect(x: -50, y: -50, width: 100, height: 100)
redlayer.contents = UIImage(systemName: "circle")?.cgImage
bluelayer.contents = UIImage(systemName: "triangle")?.cgImage
redlayer.backgroundColor = UIColor.red.cgColor
bluelayer.backgroundColor = UIColor.blue.cgColor
redlayer.borderColor = UIColor.magenta.cgColor
redlayer.borderWidth = 5
bluelayer.borderColor = UIColor.cyan.cgColor
bluelayer.borderWidth = 5
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
올..
rootLayer(redLayer) 컨텐츠와 경계선 사이에 sublayer(blueLayer)의 배경색, 컨텐츠, 경계선이 들어가 있네요.
Layers Support a Corner Radius
다음은 컨텐츠 모서리를 둥글게 만들 수 있는 방법입니다.
redlayer.cornerRadius = 50
redlayer.maskedCorners = [.layerMaxXMaxYCorner, .layerMinXMinYCorner]
bluelayer.cornerRadius = 50
bluelayer.maskedCorners = [.layerMaxXMaxYCorner, .layerMinXMaxYCorner]
redlayer.cornerRadius = 50
redlayer.maskedCorners = [.layerMaxXMaxYCorner, .layerMinXMinYCorner]
bluelayer.cornerRadius = 50
bluelayer.maskedCorners = [.layerMaxXMaxYCorner, .layerMinXMaxYCorner]
bluelayer.masksToBounds = true
redlayer.cornerRadius = 50
redlayer.maskedCorners = [.layerMaxXMaxYCorner, .layerMinXMinYCorner]
redlayer.masksToBounds = true
bluelayer.cornerRadius = 50
bluelayer.maskedCorners = [.layerMaxXMaxYCorner, .layerMinXMaxYCorner]
bluelayer.masksToBounds = true
redlayer.cornerRadius = 50
redlayer.maskedCorners = [.layerMaxXMaxYCorner, .layerMinXMinYCorner]
redlayer.masksToBounds = true
bluelayer.cornerRadius = 50
bluelayer.maskedCorners = [.layerMaxXMaxYCorner, .layerMinXMaxYCorner]
Layers Support Built-In Shadows
CALayer는 그림자 효과까지도 줄 수 있습니다.
그림자를 얼마나 줄 건지 불투명도(Opacity)를,
그림자와 컨텐츠를 얼만큼 떨어트릴 건지 간격(Offset)을 설정해야 합니다.
단, iOS와 macOS의 좌표계가 다르기 때문에 주의해야 할 필요가 있습니다.
검증 타임 😎
redlayer.shadowOpacity = 0.5
redlayer.shadowOffset = CGSize(width: 10, height: 10)
빨간색 레이어에만 그림자 효과를 추가했지만 하위 레이어까지 영향을 받는 것을 알 수 있습니다.
redlayer.shadowOpacity = 0.5
redlayer.shadowOffset = CGSize(width: 10, height: 10)
bluelayer.shadowOpacity = 0.5
bluelayer.shadowOffset = CGSize(width: 10, height: 10)
아까 위에서 빨간색 레이어에 파란색 레이어를 addSublayer할 때
빨간색 레이어의 컨텐츠와 경계선 사이에 파란색 레이어의 배경색, 컨텐츠, 경계선에 들어가 있었죠?
파란색 레이어의 그림자는 파란색 레이어의 배경색 뒤, 빨간색 레이어의 컨텐츠 위에 표시가 되네욥.
redlayer.shadowOpacity = 0.5
redlayer.shadowOffset = CGSize(width: 10, height: 10)
redlayer.masksToBounds = true
bluelayer.shadowOpacity = 0.5
bluelayer.shadowOffset = CGSize(width: 10, height: 10)
그림자도 잘라먹는 maskToBounds
경계선도 그림자가 나오네욥
In this case, the shadow is applied to the layer’s content, border, and sublayers.
Filters Add Visual Effects to OS X Views
- macOS
The Layer Redraw Policy for OS X Views Affects Performance
- macOS
Adding Custom Properties to a Layer
Core Animation은 CAAnimation 클래스와 CALayer 클래스와 관련된 NSKeyValueCoding 프로토콜을 확장하여
사용자 정의 프로퍼티를 지원할 수 있습니다.
또한 프로퍼티를 변경할 때 애니메이션이 수행되도록
프로퍼티와 CAAction을 연결할 수도 있습니다.
Printing the Contents of a Layer-Backed View
Core Animation은 일반적으로 화면에 컨텐츠를 렌더링 할 때 캐시 된 비트맵에 의존하지만
print할 때는 인쇄 환경을 수용할 필요가 있을 때 컨텐츠를 redraw합니다.
print가 무슨 의미의 print인지 보니까
엄...
암튼..
특히 layer-backed view가 레이어의 컨텐츠를 제공하기 위해 draw 메서드를 사용하는 경우
Core Animation은 인쇄된 레이어 컨텐츠를 생성하기 위해 프린트하는 동안 draw 메서드를 호출합니다.
References
'iOS' 카테고리의 다른 글
[iOS] Unit Test - 특정 파일만 Coverage 측정하기 (0) | 2022.05.16 |
---|---|
[iOS] GitHub Action - CI (0) | 2022.05.15 |
[iOS] Core Animation Programming Guide (1) Core Animation Basics (2) | 2022.02.18 |
[iOS] Object Graph와 Archive 정리 (0) | 2021.12.08 |
[iOS] NSCoding 정리 (0) | 2021.12.07 |
- Total
- Today
- Yesterday
- MeTal
- 네트워크 플로우
- 컴퓨터 추상화
- 최단경로문제
- 강한 순환 참조
- HIG
- 최대 매칭
- 벨만포드 시간복잡도
- 포드 풀커슨 알고리즘
- CompositionalLayout
- WWDC16
- mach-o
- 코딩대회
- 부스트캠프 6기
- rxswift
- 최단경로 문제
- WWDC17
- test coverage
- Testable
- CPU와 Memory
- 네트워크 유량
- WWDC19
- 벨만포드 알고리즘
- observeOn
- 다익스트라 시간복잡도
- 에드몬드 카프 알고리즘
- 최단경로 알고리즘
- IOS
- State Restoration
- WWDC21
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |