문제 상황
iOS 앱스쿨의 첫 번째 프로젝트로 진행한 'Packing' (패킹) 앱에 들어갈 여행 목록을 볼 수 있는 뷰를 구성하다가, 디자인이 복잡해지자 코드를 더 이상 수정하기 어려워지고, 다른 사람이 보기에도 깔끔하지 않은 코드가 되었다.
예를 들어 다른 팀원이 내가 구성한 background 코드를 재사용하려고 할 때, 코드가 지저분해질 수 있다.
(앱 내에서 공통으로 사용하는 배경 코드를 바꾸려고 할 때, view 마다 다시 들어가서 코드를 바꿔줘야 하는 등의 문제)
이럴 때, ViewModifier 를 사용할 수 있다.
어떻게 해결하였는가?
복잡해진 view 코드를 리팩토링 할 때, 이전처럼 some view 프로토콜을 따르는 변수들을 만들어 보려고 하였다.
struct TestView: View {
var body: some View {
VStack {
oneView
}
}
var oneView: some View {
Text("oneView")
}
}
혹은 아래와 같이 ViewBuilder 를 사용하여, 코드를 분리할 수도 있다.
struct TeskView: View {
@Environment(\.colorScheme) var colorScheme
var body: some View {
ZStack {
BackgroundGradientView(colorScheme: colorScheme)
.ignoresSafeArea()
//...
}
}
@ViewBuilder
private func BackgroundGradientView(colorScheme: ColorScheme) -> some View {
LinearGradient(gradient: Gradient(colors: colorScheme == .light ? [Color(hex: "AEC6CF"), Color(hex: "ECECEC"), Color(hex: "FFFDD0")] : [Color(hex: "34495E"), Color(hex: "555555"), Color(hex: "333333")]), startPoint: .topLeading, endPoint: .bottomTrailing)
}
}
다시 보니 이 상황에서는 굳이 ViewBuilder 를 사용할 필요는 없었다. 보통 ViewBuilder 는 조건문과 같이, 여러 상태값에 따라서 여러 뷰를 반환하고 싶을 때 사용한다.
private var EmptyStateView: some View {
VStack {
Image(systemName: "airplane")
.font(.largeTitle)
.padding()
Text("현재 여행 목록이 없습니다.\n'+'를 눌러 새 여행을 추가해주세요.")
.font(.headline)
.multilineTextAlignment(.center)
.padding()
}
}
다시 돌와와서,
복잡한 view 코드를 분리하기 위해서 이번에는 이전에 잠깐 배웠던 ViewModifier 를 사용해보았다.
TestView 안에서만이 아닌, 다른 View 에서도 재사용하기 위해서.
ViewModifier 개념 정확히 이해하기
ViewModifier: A modifier that you apply to a view or another view modifier, producing a different version of the original value.
viewBuilder 를 사용하면, background 나 font, clipShape 와 같은 modifier design code 를 다른 뷰에서도 쉽게 재사용할 수 있다.
다시 위 코드를 보면,
viewBuilder 를 사용한 코드. (다른 뷰에서 BackgroundGradientView 를 재사용 할 수 없음)
struct JourneyListView: View {
@Environment(\.colorScheme) var colorScheme
var body: some View {
ZStack {
BackgroundGradientView(colorScheme: colorScheme)
.ignoresSafeArea()
@ViewBuilder
private func BackgroundGradientView(colorScheme: ColorScheme) -> some View {
LinearGradient(gradient: Gradient(colors: colorScheme == .light ? [Color(hex: "AEC6CF"), Color(hex: "ECECEC"), Color(hex: "FFFDD0")] : [Color(hex: "34495E"), Color(hex: "555555"), Color(hex: "333333")]), startPoint: .topLeading, endPoint: .bottomTrailing)
}
}
다시 수정한 코드
struct GradientBackground: ViewModifier {
@Environment(\.colorScheme) var colorScheme
func body(content: Content) -> some View {
content
.background(LinearGradient(gradient: Gradient(colors: colorScheme == .light ? [Color(hex: "AEC6CF"), Color(hex: "ECECEC"), Color(hex: "FFFDD0")] : [Color(hex: "34495E"), Color(hex: "555555"), Color(hex: "333333")]), startPoint: .topLeading, endPoint: .bottomTrailing))
.ignoresSafeArea()
}
}
extension View {
func gradientBackground() -> some View {
modifier(GradientBackground())
}
}
struct JourneyListView: View {
var body: some View {
VStack {
//...
}
.gradientBackground()
}
}
GradientBackground 가 ViewModifier protocol 을 따르도록 하고, JourneyListView 와 같이 해당 코드를 사용하고 싶은 View 에 이것을 modifier를 추가하여 사용한다.
느낀점
확실히 코드를 하나하나 다시 작성해보면서, ViewModifier가 왜 효율적인지 실감할 수 있었다.
특히 팀원들이 내가 디자인한 View 코드를 보고 가져다 쓰려고 할 때 용이할 것 같다.
Reference
https://developer.apple.com/documentation/swiftui/viewbuilder
ViewBuilder | Apple Developer Documentation
A custom parameter attribute that constructs views from closures.
developer.apple.com
https://developer.apple.com/documentation/swiftui/viewmodifier
ViewModifier | Apple Developer Documentation
A modifier that you apply to a view or another view modifier, producing a different version of the original value.
developer.apple.com
https://www.hackingwithswift.com/books/ios-swiftui/custom-modifiers
https://phillip5094.tistory.com/222
@ViewBuilder는 언제 사용할까?
안녕하세요. ViewBuilder는 언제 사용할까요? # 예시 # some View를 반환하는 함수에서 에러 나요ㅠㅠ 간단한 예시로 Text 뷰를 반환하는 함수가 있다고 해볼게요. 여기까진 문제없어요ㅎㅎ 살짝 복잡하
phillip5094.tistory.com