# Navigation
네비게이션 기능 구현을 위해서는 NavigationStack
에 뷰를 임베딩하면 된다.
navigationTitle
로 네비게이션 뷰 타이틀을 지정한다.toolbar
Visiblity로 네비게이션 바 노출 여부를 지정한다.toolbar
content 클로저에 뷰를 전달하여 네비게이션 바 UI를 구성한다.ToolBarItemGroup
과 placement를 조합하여 네비게이션 바 내의 요소들의 위치를 조정한다.
NavigationStack {
VStack {
Text("Hello, World!")
Button(action: {
isHidden.toggle()
}, label: {
Text("Button")
})
.padding()
}
.navigationTitle("Hello")
.toolbar(isHidden ? .hidden : .visible, for: .automatic)
.toolbar {
ToolbarItemGroup(placement: .topBarLeading) {
HStack {
Button(action: {
isHidden.toggle()
}, label: {
Text("Button")
})
.padding()
}
}
ToolbarItemGroup(placement: .topBarTrailing) {
HStack {
Button(action: {
isHidden.toggle()
}, label: {
Text("Button")
})
.padding()
}
}
}
}
# Push
네비게이션 푸시는 NavigationLink
를 스택 내에 추가하면 된다.
NavigationStack {
VStack {
Text("Hello, World!")
Button(action: {
push = true
}, label: {
Text("Button")
})
.padding()
NavigationLink("Push") {
EmojiView(emoji: Emoji.smile.rawValue)
.padding()
.navigationTitle(Emoji.smile.rawValue)
}
}
.navigationTitle("Conditional Push")
}
링크 컨텐츠에 추가한 뷰가 링크 버튼 탭 이후 화면이 푸시되면서 전달된다. 푸시된 화면 내에서 백버튼 탭으로 직접 pop하는 방법 외에 툴바에 직접 닫기 버튼을 정의해도 된다. @Environment
프로퍼티 래퍼를 사용하면 된다.
struct EmojiView: View {
@Environment(\.dismiss) var dismiss
var emoji: String
var body: some View {
Text(emoji)
.font(.system(size: 200))
.toolbar {
Button {
dismiss()
} label: {
Text("Close")
}
}
}
}
# Displace
화면 Push
이외에도 화면을 표시하는 다른 방식이 존재하는데, 그 중 하나로 Displace
가 있다. 화면을 스택에 푸시하거나 팝하는 형태가 아닌 교체하는 방식으로 동작한다. 이러한 네비게이션 스타일을 Column Style
이라고 한다.
화면이 넓은 기종에서 동작한다. 기존에는 NavigationView
기반으로 동작했는데, deprecated
될 예정이어서 NavigationSplitView
로 구현해야 한다.
NavigationSplitView(sidebar: {
List {
ForEach(Emoji.allCases, id: \.self) { emoji in
NavigationLink(emoji.rawValue) {
EmojiView(emoji: emoji.rawValue)
}
}
}
.navigationTitle("Emoji")
}, detail: {
ZStack {
Color.yellow
Text("Secondary Scene")
.font(.largeTitle)
}
.ignoresSafeArea()
})
사이드바 파라미터에 화면 교체를 위한 인터랙션 목록을 전달하고, 디테일 뷰에서 첫 화면에 표시할 뷰를 구현한다. 네비게이션 링크에 전달되는 뷰가 detail
에서 표시할 뷰를 디스플레이스하는 방식으로 동작한다.
# 탭뷰
탭뷰를 통해 탭 기능을 구현할 수 있다. 탭뷰 안에 탭을 통한 전환 대상 뷰들을 정의해둔다. selection
파라미터에 인덱스와 관련된 바인딩 속성을 전달하고 각 탭뷰 요소에 tag
모디파이어를 추가하면 태그 속성값이 selection
에 바인딩된다.
기본적으로 selectedIndex
는 구현되지 않기 때문에 직접 태그를 정의해야 한다.
TabView(selection: $selectedIndex) {
SymbolScene(name: "star", color: Color.red)
.tabItem { Label("Star", systemImage: "star") }
.tag(0)
SymbolScene(name: "heart", color: Color.green)
.tabItem { Label("heart", systemImage: "heart") }
.tag(1)
SymbolScene(name: "play", color: Color.blue)
.tabItem { Label("Play", systemImage: "play") }
.tag(2)
}
.onChange(of: selectedIndex) { oldValue, newValue in
print(newValue)
}
# Selection Binding
탭뷰 셀렉션 바인딩을 통해 탭바 컨트롤러를 통한 화면 전환이 아닌 탭뷰 내에서 화면 전환도 구현할 수 있다.
struct SymbolScene: View {
@Binding var selectedIndex: Int
var name: String
var color: Color
var body: some View {
VStack {
Spacer()
Image(systemName: name)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 200, height: 200)
.foregroundColor(color)
Button(action: {
selectedIndex = selectedIndex >= 2 ? 0 : selectedIndex + 1
}, label: {
Text("Button")
})
Spacer()
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
위와 같이 바인딩속성을 추가하여 상위 탭뷰에서 각 탭뷰 요소에 생성자 파라미터로 바인딩을 전달하면 된다.
TabView(selection: $selectedIndex) {
// selectedIndex를 바인딩으로 전달
SymbolScene(selectedIndex: $selectedIndex, name: "star", color: Color.red)
.tabItem { Label("Star", systemImage: "star") }
.tag(0)
SymbolScene(selectedIndex: $selectedIndex, name: "heart", color: Color.green)
.tabItem { Label("heart", systemImage: "heart") }
.tag(1)
SymbolScene(selectedIndex: $selectedIndex, name: "play", color: Color.blue)
.tabItem { Label("Play", systemImage: "play") }
.tag(2)
}
.onChange(of: selectedIndex) { oldValue, newValue in
print(newValue)
}