본문은 영어 공부를 겸해 WWDC 영상을 보고 정리한 것으로 오역이 있을 수 있습니다.
정확한 내용은 원문 참고 바랍니다.
Overview
SwiftUI에서 커스텀 컨테이너 뷰를 만드는 새로운 방법 소개
컨테이너 뷰: 컨테이너 뷰는 클로저를 사용해 콘텐츠 랩핑
뷰 빌더에 정적 콘텐츠 정의 예: 하드 코딩 Text 뷰
뷰 빌더에 동적 콘텐츠 정의 예: ForEach 사용해서 Text 뷰
List {
// 정적 콘텐츠 정의
Text("Scrolling in the Deep")
Text("Born to Build & Run")
Text("Some body Like View")
// 동적 콘텐츠 정의
ForEach(otherSongs) { song in
Text(song.title)
}
}
Composition
Composition: Composition은 여러 개의 뷰를 결합하여 더 복잡한 UI를 구성하는 것을 의미(ChatGPT)
SwiftUI는 다양한 종류의 콘텐츠를 구성(composing)하기 위한 API를 제공한다.
예를 들어, 아래 data-driven list 코드를 ForEach를 사용하는 코드로 다시 작성할 수 있다.
두 코드는 기능적으론 동일하지만, ForEach 뷰는 뷰 빌더 내부에서 중첩 될 수 있다는 게 중요하다.
// data-driven list
List(songsFromSam) { song in
Text(song.title)
}
// ->
// data-driven ForEach list
List {
ForEach(songsFromsam) { song in
Text(song.title)
}
}
즉, List 내부에서 뷰 만을 사용해 서로 다른 콘텐츠를 결합할 수 있다는 뜻이다.
아래 unified List는 composition의 예.
List {
Text("Scrolling in the Deep")
Text("Born to Build & Run")
Text("Some body Like View")
ForEach(otherSongs) { song in
Text(song.title)
}
}
container에 유연한 composotion 지원하기: 새로운 API, ForEach(subviewOf:) 사용해서 리팩토링 이전 코드에서 단일 속성만 지원하던 것과 달리, 리팩토링한 코드는 다른 종류의 뷰도 수용할 수 있다.
var data: Data
@ViewBuilder var content: (Data.Element) -> Content
var body: some View {
DisplayBoardCardLayout {
ForEach(data) { item in
CardView {
content(item)
}
}
}
.background { BoardBackgroundView() }
}
->
@ViewBuilder var content: Content
var body: some View {
DisplayBoardCardLayout {
ForEach(subviewOf: content) { subview in
CardView {
subview
}
}
}
.background { BoardBackgroundView() }
}
DisplayBoard {
Text("Scrolling in the Deep")
Text("Born to Build & Run")
Text("Some body Like View")
ForEach(otherSongs) { song in
Text(song.title)
}
}
->
// 코드 수정 없이 바로 추가 가능
DisplayBoard {
Text("Scrolling in the Deep")
Text("Born to Build & Run")
Text("Some Body Like View")
ForEach(songsFromSam) { song in
Text(song.title)
}
ForEach(songsFromSommer) { song in
Text(song.title)
}
}
subview: 다른 뷰를 포함하고 있는 뷰
Declared subview: 명시적으로 선언된 서브 뷰
Resolved subview: 앱이 실행될 때(런타임 타임) 생성되는 실질적인 뷰
// declared subviews
Group {
Text("Scrolling in the Deep")
Text("Born to Build & Run")
Text("Some body Like View")
}
// ->
// resolved subviews
Text("Scrolling in the Deep")
Text("Born to Build & Run")
Text("Some body Like View")
ForEach(subviewOf:): 콘텐츠의 resolved subview만 반복. 따라서, 적은 코드로 컨테이너가 composition을 가능하게 만들 수 있다.
Sections
리스트는 built-in 컨테이너의 예로 section을 지원한다.
List {
Section("Favorite Songs") {
Text("Scrolling in the Deep")
Text("Born to Build & Run")
Text("Some Body Like View")
}
Section("Other Songs") {
ForEach(otherSongs) { song in
Text(song.title)
}
}
}
ForEach(sectionOf:): 커스텀 컨테이너에 header, footer를 지원할 수 있게 해준다.
@ViewBuilder var content: Content
var body: some View {
HStack(spacing: 80) {
ForEach(sectionOf: content) { section in
VStack(spacing: 20) {
if !section.header.isEmpty {
DisplayBoardSectionHeaderCard { section.header }
}
DisplayBoardSectionContent {
section.content
}
.background { BoardSectionBackgroundView() }
}
}
}
.background { BoardBackgroundView() }
}
Customization
container-specific modifier인 새로운 API 지원, container values
Container values: keyed storage, Environment와 Preferences 같은 컨셉.
뷰 계층의 아래로 전달하는 environment values, 위로 전달하는 Preferences와 다르게 컨테이너 안에서만 유효한 값이다.
뷰 계층도:

Environment values 계층도:

Preference values 계층도:

Container values 계층도:

사용 방법:
extension ContainerValues {
@Entry var isDisplayBoardCardRejected: Bool = false
}
extension View {
func displayBoardCardRejected(_ isRejected: Bool) -> some View {
containerValue(\\.isDisplayBoardCardRejected, isRejected)
}
}
struct DisplayBoardSectionContent<Content: View>: View {
@ViewBuilder var content: Content
var body: some View {
DisplayBoardCardLayout {
Group(subviewsOf: content) { subviews in
ForEach(subviews) { subview in
let values = subview.containerValues
CardView(
scale: (subviews.count > 15) ? .small : .normal,
isRejected: values.isDisplayBoardCardRejected
) {
subview
}
}
}
}
}
}
DisplayBoard {
Section("Matt's Favorites") {
Text("Scrolling in the Deep")
.displayBoardCardRejected(true)
Text("Born to Build & Run")
Text("Some Body Like View")
}
Section("Sam's Favorites") {
ForEach(songsFromSam) { song in
Text(song.title)
.displayBoardCardRejected(song.samHasDibs)
}
}
Section("Sommer's Favorites") {
ForEach(songsFromSommer) { Text($0.title) }}}
}
.displayBoardCardRejected(true)
}
'WWDC' 카테고리의 다른 글
| WWDC24: Run, Break, and Inspect Explore effective debugging in LLDB (0) | 2024.07.22 |
|---|---|
| WWDC24: Swift에서 noncopyable 유형 소비하기 (1) | 2024.07.12 |
| Teck Talks: 더 적은 데이터로 더 많은 작업하기 (0) | 2024.06.29 |
| WWDC24: Swift on Server 생태계 (0) | 2024.06.28 |
| WWDC24: Swift Tesing 소개 (0) | 2024.06.27 |