티스토리 뷰
최근 운동 서비스 앱을 SwiftUI 로 작업하다가 뷰에 그림자를 만들었습니다.
이런 UI를 그리고 싶었거든요...
그런데 이런 보라색 오류가 보였습니다.
오류를 눌러봐도 어디에 오류가 있는건지 제대로 보여주진 않았습니다.
직역해보니
음.. 렌더링 비용이 많이 드는 동적 그림자를 사용하고 있다네요.
shadowPath를 설정하거나 그림자를 이미지로 만들어서 사용하라고 권해주는데,
저는 그림자를 하나하나 이미지로 만드는 비효율적인 행동을 하고싶지는 않으니
shadowPath 를 더 알아보기로 결정했습니다.
이번 글에서는 SwiftUI에서 그림자를 최적화하여 적용하는 방법에 대해 알아보겠습니다.
특히 shadow
와 overlay
를 사용하는 방법을 비교하고, 최적화된 그림자 적용 방법에 대해 설명하겠습니다.
기존 코드: shadow
shadow
modifier를 사용한 기존 코드입니다.
struct CustomTabBarView: View {
@StateObject private var tabViewModel = TabViewModel()
var body: some View {
HStack(alignment: .center, spacing: 48) {
ForEach(tabViewModel.tabs) { tab in
TabButtonView(
iconName: tab.iconName,
isSelected: tabViewModel.selectedTab == tab.tab,
action: { tabViewModel.selectTab(tab.tab) }
)
}
}
.background(Color.white)
.cornerRadius(20)
.shadow(color: Color.black.opacity(0.12), radius: 10, x: 0, y: 0) // 여기!
.offset(y: -15)
}
}
이 코드는 간단하고 직관적이지만, 전체 뷰에 그림자를 적용하기 때문에 성능 측면에서는 최적화되지 않은 방법입니다.
SwiftUI는 그림자의 경로를 동적으로 계산해야 하기 때문에, 복잡한 뷰에서는 성능 저하가 발생할 수 있다고 합니다.
수정된 코드: overlay
& RoundedRectangle
overlay
와 RoundedRectangle
을 사용해보면 아래와 같습니다.
.overlay(
RoundedRectangle(cornerRadius: 20)
.stroke(Color.clear, lineWidth: 0)
.shadow(color: Color.black.opacity(0.12), radius: 10, x: 0, y: 0)
)
이 코드는 overlay
를 사용하여 별도의 레이어에 그림자를 적용합니다.
RoundedRectangle
을 사용하여 그림자의 형태를 명시적으로 정의하기 때문에 그림자의 경로를 최적화하여 렌더링할 수 있습니다.
왜 shadowPath
안알려줌?
UIKit의 shadowPath
는 그림자가 적용될 경로를 명시적으로 정의할 수 있게하는 CALayer
의 속성 중 하나입니다.
쓰려면 쓸수는 있는데 UIViewRepresentable
을 사용해서 UIView
를 만들고 이걸 또 SwiftUI
에 통합하는 과정이 필요합니다.
어차피 그 방법을 지금 사용하지 않을거라서 따로 설명은 하지 않겠습니다.
#shadowPath 사용 예제
더보기
import UIKit
class ShadowedView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
setupShadow()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setupShadow()
}
private func setupShadow() {
// 뷰의 기본 설정
self.backgroundColor = .white
self.layer.cornerRadius = 20
// 그림자 설정
self.layer.shadowColor = UIColor.black.cgColor
self.layer.shadowOpacity = 0.12
self.layer.shadowRadius = 10
self.layer.shadowOffset = CGSize(width: 0, height: 0)
// shadowPath 설정
let path = UIBezierPath(roundedRect: self.bounds, cornerRadius: 20).cgPath
self.layer.shadowPath = path
}
}
결론
SwiftUI에서 그림자를 적용할 때, 단순히 shadow
modifier를 사용하는 것보다 overlay
와 RoundedRectangle
을 사용하여 그림자의 형태를 명시적으로 정의하는 것이 더 효율적입니다. 시각적으로는 거의 동일하게 보이지만, 그림자의 렌더링을 최적화하기 때문에 성능 측면에서 중요한 차이를 만들 수 있습니다.
주요 차이점
- 그림자 적용 위치: 기존 코드는 전체 뷰에 직접 그림자를 적용하지만, 수정된 코드는 overlay를 사용하여 별도의 레이어에 그림자를 적용합니다. 이는 그림자의 렌더링을 분리하여 성능을 향상시킵니다.
- 그림자 형태: 수정된 코드는 RoundedRectangle을 사용하여 그림자의 형태를 명시적으로 정의합니다.
- 렌더링 최적화: overlay와 RoundedRectangle을 사용함으로써, SwiftUI는 그림자를 더 효율적으로 렌더링할 수 있습니다. 이는 shadowPath를 명시적으로 설정하는 것과 유사한 효과를 낼 수 있습니다.
라고해서 적용을 해보았는데요...
하... GPT-4o, Claude 3.5 너무 신뢰하지 말걸... 블로그 글 다써놓고 적용하는 바람에 위의 글이 무용지물이 됐습니다...
찐찐찐막 최종 해결 코드 : drawingGroup
공식문서에 이런게 있었네요...
drawingGroup(opaque:colorMode:) | Apple Developer Documentation
Composites this view’s contents into an offscreen image before final display.
developer.apple.com
func drawingGroup(
opaque: Bool = false,
colorMode: ColorRenderingMode = .nonLinear
) -> some View
drawingGroup(opaque:colorMode:) modifier는 뷰의 하위 트리를 렌더링하기 전에 단일 뷰로 평평(flat)하게 만든다고 합니다.
즉 뷰를 하나의 정적 이미지로 만들어주어 뷰 대신 비트맵으로 표시해준다네요. 😮
오늘의 배움
코드 적용을 시켜보고 글을 쓰자...^^
'Apple > iOS' 카테고리의 다른 글
[🤔] performSegue 호출시 sender에 왜 nil을 보내면 안되는걸까? (2) | 2024.04.01 |
---|---|
[Swift/iOS] UITableViewCell 4가지 Style 살펴보기 (0) | 2024.03.14 |
- Total
- Today
- Yesterday
- flutter 3.7
- tojson()
- sliver
- ios
- slivers
- lints
- flutter
- jsonkey
- flutter updates
- 플러터
- 오블완챌린지
- flutter_lints
- analysis_options
- 티스토리챌린지
- swiftmigration
- 렌더링최적화
- 오블완
- sliverlist
- 낙관적 업데이트 패턴
- 플러터 업데이트
- 개발신입
- 플러터업데이트
- fromjson()
- SWIFT
- 플러터ios애니메이션
- jsonserializable
- 다트문법
- flutter3.7
- 플러터네이티브
- llm 설치
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 | 31 |