티스토리 뷰
NavigationStack은 path라는 State 변수에 데이터를 push 함으로써 화면전환을 하는데요, 이때 .navigationDestination이라는 메서드에서 push할 뷰의 정보를 받습니다. 그런데 여러 종류의 View를 push하려면 어떻게 해야할까요?
같은 뷰, 다른 데이터라면? - Struct
같은 뷰에 각각 다른 데이터를 넣어야한다면 struct를 사용할 수 있습니다. 공식 예제에서 나오는 방법이죠!
struct ViewContent: Hashable {
let number: Int
let content: String
}
이때 Hashable 프로토콜을 준수해야하더라구요. 이 경우에는 String이라는 프리미티브 타입이 있어서 따로 구현하지 않아도 됐는데요, 만약 다른 struct 타입을 프로퍼티로 갖는 경우에는 직접 Hashable 메서드를 구현해야합니다.
// ContentView.swift
struct ContentView: View {
@State var path: [NextViewContent] = []
var body: some View {
NavigationStack(path: $path) {
VStack {
Text("Hello, Navigation Stack")
Button(action: {
// ✅ append하며 값을 전달합니다
path.append(NextViewContent(number: 0,
content: "frist content"))
}) {
Text("push next view 1")
}
Button(action: {
path.append(NextViewContent(number: 1,
content: "second content"))
}) {
Text("push next view 2")
}
}
.padding()
// ✅ View를 만들면서 데이터를 전달합니다
.navigationDestination(for: NextViewContent.self) { next in
NextView(number: next.number, content: next.content )
}
}
}
}
// NextView.swift
struct NextView: View {
var number: Int
var content: String
var body: some View {
Text("number: \(number)")
Text("content: \(content)")
}
}
첫번째로 path에 append할때 데이터를 전달해주고, 두번째로 navigationDestination에서 뷰를 만들때 받은 데이터를 전달해주시면 됩니다.
전달한 값들이 잘 나오네요!
종류가 다른 뷰를 사용한다면? - Enum
종류가 다른 View를 push하고 싶다면 종류를 구분한 Enum을 사용해주면 됩니다.
enum StackViewType {
case firstView
case secondView
}
위와 같이 뷰 종류를 정의해주고
// ContentView.swift
struct ContentView: View {
@State var path: [StackViewType] = []
var body: some View {
NavigationStack(path: $path) {
VStack {
Text("Hello, Navigation Stack")
Button(action: { path.append(.firstView) }) {
Text("push first view")
}
Button(action: { path.append(.secondView) }) {
Text("push second view")
}
}
.padding()
.navigationDestination(for: StackViewType.self) { stackViewType in
// ✅ 중요
switch stackViewType {
case .firstView:
FirstView()
case .secondView:
SecondView()
}
}
}
}
}
// FirstView.swift
struct FirstView: View {
var body: some View {
VStack {
Text("First view")
}
.padding()
.background(Color.green)
}
}
// SecondView.swift
struct SecondView: View {
var body: some View {
VStack {
Text("Second view")
}
.padding()
.background(Color.yellow)
}
}
.navigationDestination메서드에서 Enum의 type을 넣어준 후 switch case 문으로 각각 타입에 맞는 뷰를 만들어주면 됩니다.
종류가 다르면서 같은 데이터를 전달해야한다면? - Enum을 감싼 Struct
종류가 다른 뷰인데 push할때 데이터도 전달해야한다면 어떻게 할까요?
enum StackViewType {
case firstView
case secondView
}
struct StackView: Hashable {
let type: StackViewType
let content: String
}
위처럼 아까 만든 enum을 프로퍼티로 가지는 struct를 만들어주면됩니다. 현재 content는 String이지만 다른 Struct 형이 될 수도 있겠죠? 주의해야할점은 navigationDestination에 적용하기 위해서는 첫번째와 같이 Hashable을 준수해야한다는 점입니다.
// ContentView.swift
struct ContentView: View {
// ✅
@State var path: [StackView] = []
var body: some View {
NavigationStack(path: $path) {
VStack {
Text("Hello, Navigation Stack")
Button(action: {
// ✅
path.append(StackView(type: .firstView,
content: "frist content"))
}) {
Text("push first view")
}
Button(action: {
// ✅
path.append(StackView(type: .secondView,
content: "second content"))
}) {
Text("push second view")
}
}
.padding()
// ✅
.navigationDestination(for: StackView.self) { stackView in
switch stackView.type {
case .firstView:
FirstView(content: stackView.content)
case .secondView:
SecondView(content: stackView.content)
}
}
}
}
}
// FirstView.swift
struct FirstView: View {
var content: String
var body: some View {
VStack {
Text("First view: \(content)")
}
}
}
// SecondView.swift
struct SecondView: View {
var content: String
var body: some View {
VStack {
Text("Second view: \(content)")
}
}
}
이제 path에 타입을 struct로 바꾸고 path에 append할때 컨텐츠를 넣어주면 됩니다. 그리고 navigationDestination 함수에서 뷰를 새엉할때 전달받은 content를 넣어주면됩니다.
컨텐츠 값이 잘 전달되네요!
종류가 다르면서 다른 데이터를 전달해야한다면? - Struct property
NavigationStack을 중첩해서 사용하면 되지 않을까? 했지만 NavigationStack을 중첩하면 이런저런 런타임 에러가 발생하더라구요..
따라서 다음과 같은 방법을 사용해볼 수 있을 것 같습니다.
struct StackView: Hashable {
let type: StackViewType
let firstContent: String?
let secondContent: String?
}
이런식으로 첫번째 뷰에 들어갈 컨텐츠 타입과 두번째 뷰에 들어갈 컨텐츠 타입을 나눠주는 것이죠. 옵셔널 값으로 설정한후에 필요할때만 데이터를 넣는식으로 사용하면 됩니다. 예시에선 모두 String이지만 다른 Struct 타입이 들어가야할 때 유용하겠죠?
// ContentView.swift
struct ContentView: View {
@State var path: [StackView] = []
var body: some View {
NavigationStack(path: $path) {
VStack {
Text("Hello, Navigation Stack")
Button(action: {
// ✅
path.append(StackView(type: .firstView,
firstContent: "frist content",
secondContent: nil))
}) {
Text("push first view")
}
Button(action: {
// ✅
path.append(StackView(type: .secondView,
firstContent: nil,
secondContent: "second content"))
}) {
Text("push second view")
}
}
.padding()
// ✅
.navigationDestination(for: StackView.self) { stackView in
switch stackView.type {
case .firstView:
FirstView(content: stackView.firstContent ?? "")
case .secondView:
SecondView(content: stackView.secondContent ?? "")
}
}
}
}
}
// FirstView.swift
struct FirstView: View {
var content: String
var body: some View {
VStack {
Text("First view: \(content)")
}
.padding()
.background(Color.green)
}
}
// SecondView.swift
struct SecondView: View {
var content: String
var body: some View {
VStack {
Text("Second view: \(content)")
}
.padding()
.background(Color.yellow)
}
}
이런 식으로 필요하지 않을땐 nil을 넣어줍니다. 단, 컴파일 타임에서 검사해주지 않으므로 직접 type에 따라 적절히 nil을 넣어주어야한다는 위험성이 존재하게 됩니다...🥲
결과는 3번과 동일합니다...!!
마치며
사실 MVVM(혹은 다른 아키텍처)으로 ViewModel(혹은 Reactor, Store, Intent, Controller 등등..)을 사용하고 있다면 ViewModel이 대부분의 데이터를 관리할테니 NavigationStack을 조정하는 루트 뷰에서 뷰모델의 참조를 전달하는 방식으로 하면 Enum방식만으로도 여러 컨텐츠를 표시할 수 있겠죠? 또, push되는 뷰가 뷰모델을 따로 가지는 뷰라면 push할 때 다음 뷰모델을 생성해 주입하면서 뷰모델에 데이터를 전달해야하니... 조금 상황이 달라지기도하네요 ㅎㅎ 따라서 위 방식들은 이런 식으로 사용할 수 있다~ 정도로 참고만 해주세요!
읽어주셔서 감사합니다!
ref.
'iOS > SwiftUI' 카테고리의 다른 글
[SwiftUI] LinkNavigator (2) - 탭바 만들기, 네비게이션바 커스텀 (2) | 2023.05.03 |
---|---|
[SwiftUI] LinkNavigator (1) - 기본 사용법, 초기 설정 (4) | 2023.05.02 |
[iOS] 너무 커진 ViewModel 분리하기 (MVVM) (0) | 2023.04.28 |
[SwiftUI][Combine] 프로퍼티 래퍼 어노테이션 만들기(Property Wrapper Anotation) (0) | 2023.04.07 |
[SwiftUI] 데이터 바인딩 어노테이션(프로퍼티 래퍼) 정리- @State, @Binding, @ObservedObject, @StateObject, @EnvironmentObject (0) | 2022.07.29 |
- Total
- Today
- Yesterday
- coordinator pattern
- Swift Concurrency
- ios
- Flutter
- GetX
- design pattern
- combine
- 노션
- 프로그래머스
- MVC
- 아키텍쳐 패턴
- DocC
- MVVM
- 리액티브 프로그래밍
- 소프트웨어마에스트로
- 비동기/동기
- reactive programming
- 코디네이터 패턴
- TestCode
- Bloking/Non-bloking
- Flux
- SWM
- notion
- SwiftUI
- RX
- programmers
- Architecture Pattern
- MVI
- healthkit
- swift
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |