在 App 中,常常會需要「切換頁面」的行為,這時候就可以透過 導航 (Navigation) 與 分頁 (Tab) 來達成。今天先來介紹導航的部分。
在 iOS 16 之後,NavigationStack
取代了之前的 NavigationView
,提供更現代化、靈活的導航方式。
它的核心概念是:用「路徑 (path)」來管理畫面的堆疊。
struct ContentView: View {
@State private var path = NavigationPath()
var body: some View {
NavigationStack(path: $path) {
VStack {
Button("前往詳細頁") {
path.append("Detail")
}
Button("前往搜尋頁") {
path.append("Search")
}
Button("Go to Detail 2") {
path.append(2) // Can append different types
}
Button("登出") {
path.append(false)
}
}
.navigationDestination(for: String.self) { value in
if value == "Detail" {
DetailView()
} else if value == "Search" {
SearchView()
}
}
.navigationDestination(for: Int.self) { detailInt in
Text("Detail View for number: \(detailInt)")
.navigationTitle("Number Detail")
}
.navigationDestination(for: Bool.self) { value in
Text("登出成功")
}
.navigationTitle("首頁")
}
}
}
struct DetailView: View {
var body: some View {
Text("這是詳細頁")
.navigationTitle("詳細資料")
}
}
struct SearchView: View {
var body: some View {
VStack {
Text("這是搜尋頁")
.navigationTitle("搜尋資料")
}
}
}
NavigationStack
:導航容器,管理頁面切換。path.append
:進行跳頁行為navigationDestination
:根據path.append後面傳遞的參數來決定要前往哪一頁.navigationTitle("首頁")
:設定上方導覽列標題。navigationDestination支援 任何型別,因此可以用不同的資料來決定跳轉目標。。
有了 path
,返回就很簡單:
返回上一頁:移除最後一個元素
router.path.removeLast()
返回首頁:清空 path
router.path = .init()
如果我們希望「子頁面也能控制返回行為」,就需要建立一個 Router
類別,並透過 @EnvironmentObject
傳遞。
final class Router: ObservableObject {
@Published var path = NavigationPath()
}
struct ContentView: View {
@EnvironmentObject var router: Router
var body: some View {
NavigationStack(path: $router.path) {
VStack {
Button("前往詳細頁") {
router.path.append("Detail")
}
Button("前往搜尋頁") {
router.path.append("Search")
}
Button("Go to Detail 2") {
router.path.append(2) // Can append different types
}
Button("登出") {
router.path.append(false)
}
}
.navigationDestination(for: String.self) { value in
if value == "Detail" {
DetailView()
} else if value == "Search" {
SearchView()
}
}
.navigationDestination(for: Int.self) { detailInt in
Text("Detail View for number: \(detailInt)")
.navigationTitle("Number Detail")
}
.navigationDestination(for: Bool.self) { value in
Text("登出成功")
}
.navigationTitle("首頁")
}.environmentObject(router)
}
}
struct DetailView: View {
@EnvironmentObject var router: Router
var body: some View {
VStack {
Text("這是詳細頁")
.navigationTitle("詳細資料")
Button("返回上一頁") {
router.path.removeLast()
}
}
}
}
struct SearchView: View {
@EnvironmentObject var router: Router
var body: some View {
VStack {
Text("這是搜尋頁")
.navigationTitle("搜尋資料")
Button("返回首頁") {
router.path = .init()
}
}
}
}
#Preview {
ContentView().environmentObject(Router())
}
如果要做「底部導覽列」,就可以用 TabView
。
每個 Tab 就是一個獨立的畫面,可以跟 NavigationStack 搭配使用。
struct TabRootView: View {
var body: some View {
TabView {
ContentView()
.tabItem {
Label("首頁", systemImage: "house.fill")
}
SearchView()
.tabItem {
Label("搜尋", systemImage: "magnifyingglass")
}
ProfileView()
.tabItem {
Label("我的", systemImage: "person.fill")
}
}
}
}
struct ProfileView: View {
var body: some View {
Text("這是個人頁面")
.navigationTitle("我的資料")
}
}
TabView
→ 管理多個分頁.tabItem { Label(...) }
→ 設定分頁名稱與圖示path
控制堆疊。