안녕하세요.
이번에는 "Canvas 뷰를 썼을 때 정말 성능이 좋아질까??"에 대해 알아볼게요.
전체 프로젝트는 github에 올려두었습니다.
SwiftUI에는 Canvas 뷰라는 것이 있어요.
Canvas 뷰를 사용하면 복잡한 도형을 그려야 할 경우에 더 좋은 성능을 보여준다고 해요.
However, it might provide better performance for a complex drawing that involves dynamic data. Use a canvas to improve performance for a drawing that doesn’t primarily involve text or require interactive elements.
정말 그런지 확인하기 위해 저희는
위 화면을 3가지 방법으로 만들어볼 거예요.
- Shape를 이용해서
- Canvas를 이용해서
- 이미지를 이용해서
# 0. 다이아몬드 만들기
Shape로 만들든, Canvas로 만들든 다이아몬드 모양은 똑같이 재사용할 것이기 때문에 다이아몬드 모양을 먼저 만들어줄게요.
struct DiamondShape: Shape {
func path(in rect: CGRect) -> Path {
let size = min(rect.width, rect.height)
let xOffset = (rect.width > rect.height) ? (rect.width - rect.height) / 2.0 : 0.0
let yOffset = (rect.width > rect.height) ? 0.0 : (rect.height - rect.width) / 2.0
func offsetPoint(p: CGPoint) -> CGPoint {
return CGPoint(x: p.x + xOffset + rect.origin.x, y: p.y + yOffset + rect.origin.y)
}
return Path { path in
path.move(to: offsetPoint(p: CGPoint(x: 0, y: size * 0.5)))
path.addQuadCurve(to: offsetPoint(p: CGPoint(x: size * 0.5, y: 0)),
control: offsetPoint(p: CGPoint(x: size * 0.4, y: size * 0.4)))
path.addQuadCurve(to: offsetPoint(p: CGPoint(x: size, y: size * 0.5)),
control: offsetPoint(p: CGPoint(x: size * 0.6, y: size * 0.4)))
path.addQuadCurve(to: offsetPoint(p: CGPoint(x: size * 0.5, y: size)),
control: offsetPoint(p: CGPoint(x: size * 0.6, y: size * 0.6)))
path.addQuadCurve(to: offsetPoint(p: CGPoint(x: 0, y: size * 0.5)),
control: offsetPoint(p: CGPoint(x: size * 0.4, y: size * 0.6)))
path.closeSubpath()
}
}
}
# 1. Shape로 화면 구성
Shape 기반의 카드를 만들어볼게요.
struct CardShapeView: View {
var rows: Int
var cols: Int
var body: some View {
GeometryReader { proxy in
let width = proxy.size.width / CGFloat(cols)
let height = proxy.size.height / CGFloat(rows)
VStack(spacing: 0) {
ForEach(0..<rows) { i in
let rowCols = (i % 2 == 0) ? cols: cols - 1
HStack(spacing: 0) {
ForEach(0..<rowCols) { _ in
DiamondShape()
.fill(Color(red: 250/255, green: 100/255, blue: 90/255))
.frame(width: width, height: height)
}
}
}
}
}
.padding(30)
.frame(width: 300, height: 200)
.background(Color(red: 30/255, green: 40/255, blue: 60/255))
.cornerRadius(30)
.overlay(RoundedRectangle(cornerRadius: 40)
.stroke(Color(red: 0.93, green: 0.94, blue: 0.77), lineWidth: 50)
.cornerRadius(30))
.overlay(RoundedRectangle(cornerRadius: 20)
.stroke(Color(red: 250/255, green: 100/255, blue: 90/255), lineWidth: 10)
.cornerRadius(15)
.padding(20))
.overlay(RoundedRectangle(cornerRadius: 30)
.stroke(.black, lineWidth: 2)
.cornerRadius(30))
}
}
그리고 50개의 CardShapeView가 들어간 ScrollView를 만들어줄게요.
struct MultipleShapeView: View {
var body: some View {
ZStack {
Color(red: 214/255, green: 232/255, blue: 248/255)
.edgesIgnoringSafeArea(.all)
ScrollView {
ForEach(1...50, id: \.self) { _ in
CardShapeView(rows: 7, cols: 21)
}
}
.navigationTitle("50 Shape Cards")
}
}
}
일단 화면만 만들어두고 구체적인 성능 비교는 본문 맨 아래에서 할게요.
다음엔 Canvas 기반의 화면을 만들어봅시다.
# 2. Canvas로 화면 구성
Canvas 기반의 카드를 만들고,
struct CardCanvasView: View {
var rows: Int
var cols: Int
var body: some View {
Canvas { context, size in
for r in 0..<rows {
let rowCols = (r % 2 == 0) ? cols: cols - 1
for c in 0..<rowCols {
let w = size.width / CGFloat(cols)
let h = size.height / CGFloat(rows)
var x = w * CGFloat(c)
var y = h * CGFloat(r)
if r % 2 == 1 {
x = w * CGFloat(c) + w * 0.5
y = h * CGFloat(r)
}
context.fill(DiamondShape().path(in: CGRect(x: x, y: y, width: w, height: h)), with: .color(red: 250/255, green: 100/255, blue: 90/255))
}
}
}
.padding(40)
.background(Color(red: 30/255, green: 40/255, blue: 60/255))
.frame(width: 300, height: 200)
.cornerRadius(30)
.overlay(RoundedRectangle(cornerRadius: 40)
.stroke(Color(red: 0.93, green: 0.94, blue: 0.77), lineWidth: 50)
.cornerRadius(30))
.overlay(RoundedRectangle(cornerRadius: 20)
.stroke(Color(red: 250/255, green: 100/255, blue: 90/255), lineWidth: 20)
.cornerRadius(15)
.padding(25))
.overlay(RoundedRectangle(cornerRadius: 30)
.stroke(.black, lineWidth: 2)
.cornerRadius(30))
}
}
그리고 50개의 CardCanvasView가 들어간 ScrollView를 만들어줄게요.
struct MultiCanvasView: View {
var body: some View {
ZStack {
Color(red: 214/255, green: 232/255, blue: 248/255)
.edgesIgnoringSafeArea(.all)
ScrollView {
ForEach(1...50, id: \.self) { i in
CardCanvasView(rows: 7, cols: 21)
}
}
.navigationTitle("50 Canvas Card")
}
}
}
# 3. 이미지로 화면 구성
아래 이미지를 Assets에 추가하고
이미지 기반의 카드를 만듭니다.
struct CardImageView: View {
var body: some View {
Image("card-background")
.resizable()
.scaledToFill()
.clipShape(RoundedRectangle(cornerRadius: 40))
.frame(width: 300, height: 200)
.cornerRadius(30)
.overlay(RoundedRectangle(cornerRadius: 30)
.stroke(.black, lineWidth: 2)
.cornerRadius(30))
}
}
그리고 50개의 CardImageView가 들어간 ScrollView를 만들어줄게요.
struct MultiImageView: View {
var body: some View {
ZStack {
Color(red: 214/255, green: 232/255, blue: 248/255)
.edgesIgnoringSafeArea(.all)
ScrollView {
ForEach(1...50, id: \.self) { _ in
CardImageView()
}
}
.navigationTitle("50 Image Card")
}
}
}
# 4. 성능 비교
## 4.1 Memory
우선 메모리 사용량부터 살펴볼게요.
Shape로 구성된 화면을 출력할 때 상당한 메모리를 사용하는 것으로 확인됩니다.
Canvas로 구성된 화면은 Shape로 구성된 화면과 비교했을 때 상대적으로 메모리를 적게 사용한 것을 볼 수 있습니다.
Image로 구성된 화면은 Canvas로 구성된 화면보다 메모리를 미세하게 적게 사용했어요.
## 4.2 CPU
이번엔 CPU 사용량을 볼까요?
Shape로 구성된 화면을 스크롤 했을 땐, CPU 사용량이 매우 높았어요.
Canvas로 구성된 화면을 스크롤 했을 땐, 화면 첫 로딩 때 CPU 사용량이 증가했다가 이후 안정적인 성능을 보였습니다.
Image로 구성된 화면을 스크롤 했을 땐, 화면 첫 로딩 때부터 안정적인 성능을 보였습니다.
## 4.3 결론
Memory, CPU 사용량을 봤을 때, Canvas 기반 화면이 Shape 기반 화면보다 더 좋은 성능을 보여줬어요.
그리고 Image 기반 화면의 성능도 매우 좋았는데요,
만약 "화면에 출력할 모양이 동적으로 변할 필요 없이 항상 동일한 모양이다!" 하면 Canvas보다 Image를 사용하는 것이 성능이 더 좋을 것이고,
만약 "화면에 출력할 모양이 동적으로 변하는데 상당히 복잡한 모양이다!" 하면 Canvas를 사용하는 것이 성능 향상에 도움이 될 것 같아요.
간단한 모양이면 Shape를 쓰든, Canvas를 쓰든 큰 차이는 없겠죠?? 😁 😁
## 참고
- https://swdevnotes.com/swift/2022/better-performance-with-canvas-in-swiftui/
이번 글은 여기서 마무리.
'SwiftUI' 카테고리의 다른 글
__printChanges (0) | 2022.03.23 |
---|---|
SceneStorage (0) | 2022.03.20 |
cornerRadius (0) | 2022.03.13 |
SwiftUI에서 testable 한 코드 만들기 (0) | 2022.03.05 |
animatableData (0) | 2022.03.03 |