iyOmSd/Title: SwiftUI

[SwiftUI] NavigationStack

냄수 2023. 5. 29. 15:29
반응형

안녕하세요!

SwiftUI에서 항상 느꼇던 불편한점중 하나가 네비게이션이였는데요

이를 해결해주는게 나온지 좀 됐지만 이제 해보려합니다!

(진작에 나왔을 녀석이여야 했는데...)

iOS16 타겟을 쓸일이 없지만... 곧 쓸 지도 모르니까요! 😁

타입은 새롭지만 저희한텐 익숙합니다

push pop되는 그런 인터페이스를 나타내기때문이죠!

 

이것말고도 NavigationSplitView 타입도 있어요 mac이나 iPad에서 사용하는 다중열을 보여줄 때 사용하는 타입이죠

NavigationSplitView은 스킵하고 오늘은 NavigationStack을 알아보려합니다

 

이전에는 NavigationLink를 이용한 방식으로


  
var body: some View {
NavigationView {
NavigationLink {
뷰(color: .red, order: 1)
} label: {
Text("타이틀")
}
}
}

이렇게 타이틀과 뷰를 넣어주는 식으로 사용됬다면

 

새로운 버전으로는 뷰 대신에 값을 전달합니다.


  
var body: some View {
NavigationStack {
NavigationLink(value: Color.brown) {
뷰(color: .orange, order: 2)
}
.navigationDestination(for: Color.self) { color in
뷰(color: color, order: 3)
}
}
}

NavigationLink에 hashable한 value를 넣어주면

.navigationDestination 메서드로 값을 받아와서 값에따라 원하는 뷰를 그려줄 수 있어요

 

NavigationStack은 Stack이 표시하는 모든데이터를 나타내는 경로를 추적할 수 있어요

위의 예제는 생성자에서 path를 생략했지만 필요에 따라서 path를 바인딩 해줄 수 있어요

 

루트만 표시하는경우 path는 비어있고

스택내부 또는 스택에 푸시된 뷰 내부에서 선언된 모든 네비게이션 대상을 추적합니다

경로가 비었기때문에 위의 그림처럼 푸시된 뷰 목록도 비어있는 상태입니다.

 

Apple Pie 목록을 클릭해서 푸시된다면

path에 추가되고

스택은 경로값에 대상을 매핑하여 스택에서 푸시할 뷰를 결정합니다

 

뒤로가기 버튼을 누르면 스택이 경로 및 푸시된 뷰에서 마지막 항목을 제거합니다.

 

path를 사용한 네비게이션은 간단하게 배열에 값을 추가하고 제거하는 과정을 통해 라우팅을 컨트롤 할 수 있어요


  
struct TestNavigationStack: View {
@State private var path: [Color] = []
var body: some View {
NavigationStack(path: $path) {
VStack {
Button {
path.append(.yellow)
} label: {
Text("나랑노랑")
}
}
.navigationDestination(for: Color.self) { color in
뷰(color: color, order: 3)
.overlay {
VStack {
Button {
path.append([Color.red,Color.orange,Color.blue,Color.gray,Color.indigo].randomElement()!)
} label: {
Text("랜덤푸시")
}
Button {
path.removeAll()
} label: {
Text("초기화")
}
}
}
}
}
}
}

뒤로가기, root로 이동

 

이를 이용해서 이젠 SwiftUI에서도 코디네이터 방식도 손쉽게 해볼 수 있을 것 같다고 생각들었어요

간단하게 프로토타입만 작성해봤는데 동작은 잘 하더라구요


  
@main
struct test_swiftUIApp: App {
@StateObject var coordinator: Coordinator = Coordinator()
var body: some Scene {
WindowGroup {
NavigationStack(path: $coordinator.route) {
TestNavigationStack()
.environmentObject(coordinator)
.navigationDestination(for: Color.self) { color in
뷰(color: color, order: 3)
.overlay {
VStack {
Button {
coordinator.push([Color.red,Color.orange,Color.blue,Color.gray,Color.indigo].randomElement()!)
} label: {
Text("랜덤푸시")
}
Button {
coordinator.popToRoot()
} label: {
Text("초기화")
}
}
}
}
}
}
}
}

  
struct TestNavigationStack: View {
@EnvironmentObject var coordinator: Coordinator
var body: some View {
VStack {
Button {
coordinator.push(.yellow)
} label: {
Text("나랑노랑")
}
}
}
}

  
final class Coordinator: ObservableObject {
@Published var route: [Color] = []
func push(_ color: Color) {
route.append(color)
}
func pop() {
route.removeLast()
}
func popToRoot() {
route.removeAll()
}
}

 

NavigationStack을 사용할 때 주의할 점으로

NavigationStack 안쪽에 navigationDestination가 존재해야해요

인식하지 못하기 때문에 바깥에 쓴것과 navigationDestination를 정의하지 않는 것과 동일하죠

두 경우 모두 경고표시의 뷰를 볼 수 있습니다.

 

 

 

 

참조

https://developer.apple.com/wwdc22/10054

반응형