티스토리 뷰
안녕하세요 :) Zedd입니다.
오늘은 Custom View Modifier만들기 및...이걸로 버전 분기 꼼수..
# 목적
이번 글에서는
1. View Modifier가 어떤것인지
2. Custom View Modifier를 만드는 방법
3. (번외) Custom View Modifier를 사용한 버전 분기 (꼼수)..
를 다룬다.
# View Modifier
SwiftUI에는 ViewModifier라는 프로토콜이 존재한다.
이 modifier를 적용하면 View의 원래 값의 다른 버전을 생성하게 된다.
어렵게 생각할 필요 없이, View Modifier는 그냥 우리가 늘상 쓰는
struct ContentView: View {
var body: some View {
Text("Zedd")
.font(.caption2)
.padding(10)
.overlay(
RoundedRectangle(cornerRadius: 15)
.stroke(lineWidth: 1)
)
.foregroundColor(Color.blue)
}
}
.font, .padding() 이런것들이 다 (View) Modifier다
# Custom View Modifier 만들기
언제 Custom View Modifier를 만들면 좋을까?
예를 들어, 내가 앱 내에서 전체적으로 쓰이는 텍스트 스타일이 있다고 해보자.
struct ContentView: View {
var body: some View {
Text("Zedd")
.font(.caption2)
.padding(10)
.overlay(
RoundedRectangle(cornerRadius: 15)
.stroke(lineWidth: 1)
)
.foregroundColor(Color.blue)
}
}
뭐 이런식...근데 앱내에서 이게 너무 자주 쓰여서
1. 모든 텍스트에 저 modifier를 붙혀주는게 귀찮음
2. 스타일이 변경/추가/삭제되면 모든 텍스트에 붙어있는 스타일을 수정해야함.
그냥 아래와 같이
struct ContentView: View {
var body: some View {
Text("Zedd")
.asPrimaryCaption2()
}
}
이렇게 Custom View Modifier를 만들어 적용하면 가독성 증가 및 관리도 쉬워질것이다.
그렇다면 Custom View Modifier를 만들어보자.
위에서 말했듯이 ViewModifier는 프로토콜이다.
당연히 이를 conform해야 Custom View Modifier를 만들 수 있다.
1. ViewModifier conform
struct PrimaryCaption2Text: ViewModifier { }
ViewModifier를 conform하는 타입을 만든다.
2. body 정의 및 구현
ViewModifier는 body 메소드를 요구한다.
struct PrimaryCaption2Text: ViewModifier {
func body(content: Content) -> some View {
content
.font(.caption2)
.padding(10)
.overlay(
RoundedRectangle(cornerRadius: 15)
.stroke(lineWidth: 1)
)
.foregroundColor(Color.blue)
}
}
공통스타일을 여기에 정의해준다.
3. 위 Modifier를 호출해주는 메소드 생성
이제 만들어야 할 것은
struct ContentView: View {
var body: some View {
Text("Zedd")
.asPrimaryCaption2()
}
}
저 asPrimaryCaption2이다.
View를 conform하는 모든 타입들은 해당 modifier를 호출할 수 있어야 하므로
extension View {
func asPrimaryCaption2() -> some View {
modifier(PrimaryCaption2Text())
}
}
View를 extension하여 modifier을 만들어준다.
(modifier메소드는 그냥 새로운 modifier를 만들어주는 메소드라고 보면 된다.)
4. 사용
struct ContentView: View {
var body: some View {
Text("Zedd")
.asPrimaryCaption2()
}
}
사실
struct ContentView: View {
var body: some View {
Text("Zedd")
.modifier(PrimaryCaption2Text()) // ✅
}
}
이런식으로 바로 View에 .modifier를 호출해도 되지만
View를 extension하는 방식이 보다 일반적이고 관용적인 접근이라고 한다. (from 애플)
# Custom View Modifier를 사용한 버전 분기 (꼼수)
iOS 15에서 SwiftUI에 refreshable modifier가 나왔다.
하지만 내 앱의 최소버전은 iOS 14고.. refreshable을 쓰려면 View를 분리하는 수 밖에 없다.
(분리하는 것 말고 다른 방법이 있는지 모르겠음..)
분리하기는 싫고..요걸 Custom Modifier로 꼼수를 부릴 수 있지 않을까..싶었다.
ScrollView {
...
}
.refreshIfPossible()
이런식..!?
위 #Custom View Modifier 만들기에서 했던 순서를 그대로 따라해보자.
1. ViewModifier conform
2. body 정의 및 구현
3. 위 Modifier를 호출해주는 메소드 생성
4. 사용
1. ViewModifier conform
struct Refresh: ViewModifier {}
2. body 정의 및 구현
struct Refresh: ViewModifier {
let completion: () -> Void
init(completion: @escaping () -> Void) {
self.completion = completion
}
func body(content: Content) -> some View {
if #available(iOS 15.0, *) {
content
.refreshable { self.completion() }
} else {
content
}
}
}
그냥 이해하기 쉽게 completion으로 작성했는데,
async/await을 사용한 코드는 하단의 [개선 (feat. concurrency)]에서 다룬다.
3. 위 Modifier를 호출해주는 메소드 생성
extension View {
func refreshIfPossible(completion: @escaping () -> Void) -> some View {
modifier(Refresh(completion: completion))
}
}
4. 사용
struct ContentView: View {
var body: some View {
NavigationView {
List(1..<10) { row in
Text("Row \(row)")
}
.refreshIfPossible {
print("Zedd")
}
}
}
}
[결과]
[고려사항]
당연히 위 꼼수는
iOS 14 -> refresh 기능 X
iOS 15 -> refresh 기능 O
라서 유저를 위한;;; 개발 방법은 아니라고 생각한다.
근데 나는 그냥 개인적으로 만드는 앱이기도 하고 반드시 SwiftUI의 refreshable을 쓰고싶었기 때문에 ^^....... !! 그냥 꼼수...
(날 이렇게 만든건 refreshable을 iOS 15에 내준 애플임을 밝힘)
[개선 (feat. concurrency)]
extension View {
func refreshIfPossible(_ action: @escaping () async -> Void) -> some View {
modifier(Refresh(action))
}
}
struct Refresh: ViewModifier {
let action: () async -> Void
init(_ action: @escaping () async -> Void) {
self.action = action
}
func body(content: Content) -> some View {
if #available(iOS 15.0, *) {
content
.refreshable { await action() }
} else {
content
}
}
}
struct ContentView: View {
var body: some View {
NavigationView {
List(1..<10) { row in
Text("Row \(row)")
}
.refreshIfPossible {
await requestSomething()
}
}
}
}
이렇게 하는거 맞겠지...
'SwiftUI' 카테고리의 다른 글
[SwiftUI] NavigationView ➡️ NavigationStack (2) | 2022.11.20 |
---|---|
SwiftUI ) ViewBuilder (0) | 2021.12.05 |
[Xcode 13] SwiftUI Picker Style Issue (0) | 2021.12.04 |
SwiftUI, iOS 15+ ) onAppear()대신 task() (0) | 2021.09.04 |
SwiftUI ) listRowSeparator / listSectionSeparator (0) | 2021.08.21 |
- ios 13
- Accessibility
- Git
- WWDC
- UIBezierPath
- swift sort
- github
- swift 공부
- Swift
- swift array
- Xcode
- actor
- swift delegate
- 제이슨 파싱
- IOS
- np-hard
- np-complete
- 회고
- Combine
- SwiftUI
- swift3
- 피아노
- WidgetKit
- fastlane
- WKWebView
- swift tutorial
- 스위프트 문법
- iOS delegate
- 스위프트
- FLUTTER
- Total
- Today
- Yesterday