티스토리 뷰

먼저 컨테이너 뷰가 뭘까요?

 

Apple 문서를 인용하자면...

SwiftUI provides a range of container views that group and repeat views. Use some containers purely for structure and layout, like stack views, lazy stack views, and grid views. Use others, like lists and forms, to also adopt system-standard visuals and interactivity

말 그대로 View를 Contain 하는 View를 컨테이너 뷰라고 하겠습니다. 가장 대표적으로 VStack, HStack 등이 있겠네요. 하지만 애플에서 기본적으로 제공되는 뷰 만으로는 한계가 있을 때가 있습니다.

 

역시 예시를 드는게 제일 좋겠죠?

다음과 같은 뷰를 카드뷰를 만들어야 한다고 가정해볼게요

가장 먼저 생각나는 방법은

struct NaiveCardView: View {
    let title: String
    var body: some View {
        Text(title)
            .padding()
            .background(Color.white)
            .cornerRadius(10)
            .shadow(radius: 5)
    }
}

일것 같네요

PS. UIKit이랑 비교하면 훨 편하죠....

물론 카드뷰가 Text만 감싸는 거면 상관없겠지만 Text가 아닌 Label이나 Image를 감싸야한다면?
그때마다 CardTextView, CardLabelView를 따로 만드는 건 비효율적이겠죠

그러면 어떠한 View든 받을 수 있도록 제너릭으로 바꿔줄게요

struct GenericCardView<Content: View>: View {
    var content: () -> Content
    var body: some View {
        Group(content: content)
            .padding()
            .background(Color.white)
            .cornerRadius(10)
            .shadow(radius: 5)
    }
}

그리고 마치 Text를 VStack으로 감싸듯 GenericCardView로 감싸주면 됩니다.

GenericCardView {
    Text("Hello")
}

그리고 이제는 Text 외에도 다른 View도 이용할 수 있습니다

GenericCardView {
    Image(systemName: "star")
}

하지만 이것도 한계가 있습니다

 

 

View를 한계 이상 넘겨주려 하면 컴파일이 안됩니다!

물론 View들을 다 Group로 감싸주면 해결은 되지만... 매번 감싸주는 게 그닥 좋은 방법은 아닌 것 같네요

GenericCardView {
    Group {
        Text("Hello")
        Image(systemName: "star")
    }
}

다행히도 매우 간단한 해결법이 있습니다

content 클로저를 @ViewBuilder 로 annotate해 주면 됩니다

 

잠깐?! ViewBuilder가 뭘까요?

애플이 SwiftUI를 공개하던 시점에 Swift에도 변화가 생겼는데 result builders 라는 개념이 생겼습니다.
이러한 result builder중에 View에 특화된 친구가 ViewBuilder입니다

이 친구가 어떤 일을 하는지 코드를 살짝 엿보면...

@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
extension ViewBuilder {
    public static func buildBlock<C0, C1>(_ c0: C0, _ c1: C1) -> TupleView<(C0, C1)> where C0 : View, C1 : View
}

한개 이상의 View를 받아서 하나의 TupleView로 반환해주고 있습니다.

결과적으로는 여러 뷰를 하나의 뷰로 묶어주는 역할을 한다고 보면 될 것 같습니다.

자세한 얘기는 아래 링크를 확인하시면 될 것 같습니다

https://github.com/apple/swift-evolution/blob/main/proposals/0289-result-builders.md

 

결론적으로는 init 단계에서 @ViewBuilder annotaion을 추가해주면 됩니다!

struct GenericCardView<Content: View>: View {
    var content: () -> Content

    init(@ViewBuilder content: @escaping () -> Content) {
        self.content = content
    }

    var body: some View {
        Group(content: content)
            .padding()
            .background(Color.white)
            .cornerRadius(10)
            .shadow(radius: 5)
    }
}

 

 

PS.

하지만... 카드 뷰를 만드는 방법이 이것 하나뿐일까요?

방금 만든 카드뷰를 한번 다시 살펴보면

.padding()
.background(Color.white)
.cornerRadius(10)
.shadow(radius: 5)

여러 뷰 모디파이어를 합친 게 다입니다.

그러면 굳이 뷰를 만들지 않고 커스텀 뷰 모디파이어를 만들어도 되지 않을까요?

자세한 건 다음 글에....

 

 

 

참고

https://www.swiftbysundell.com/tips/creating-custom-swiftui-container-views/

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
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
글 보관함