溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

SwiftUI怎么自定義導(dǎo)航

發(fā)布時間:2022-06-06 13:59:24 來源:億速云 閱讀:147 作者:iii 欄目:開發(fā)技術(shù)

這篇文章主要介紹“SwiftUI怎么自定義導(dǎo)航”的相關(guān)知識,小編通過實際案例向大家展示操作過程,操作方法簡單快捷,實用性強,希望這篇“SwiftUI怎么自定義導(dǎo)航”文章能幫助大家解決問題。

前言

默認情況下,SwiftUI提供的各種導(dǎo)航API在很大程度上是以用戶直接輸入為中心的——也就是說,導(dǎo)航是在系統(tǒng)響應(yīng)例如按鈕的點擊和標簽切換等事件時由系統(tǒng)本身處理的。

然而,有時我們可能想更直接地控制應(yīng)用程序的導(dǎo)航執(zhí)行方式,盡管SwiftUI在這方面仍然不如UIKit或AppKit靈活,但它確實提供了相當多的方法,讓我們在構(gòu)建的視圖中執(zhí)行完全自定義的導(dǎo)航。

切換標簽(tabs)

讓我們先來看看我們?nèi)绾文芸刂飘斍霸赥abView中顯示的標簽。通常情況下,當用戶手動點擊每個標簽欄中的一個項目時,標簽就會被切換,但是通過在一個給定的TabView中注入一個選擇(selection)綁定,我們可以觀察并控制當前顯示的標簽。在這里,我們要做的就是在兩個標簽之間切換,這兩個標簽是用整數(shù)0和1標記的:復(fù)制

struct RootView: View {
    @State private var activeTabIndex = 0
    var body: some View {
        TabView(selection: $activeTabIndex) {
            Button("Switch to tab B") {
                activeTabIndex = 1
            }
            .tag(0)
            .tabItem { Label("Tab A", systemImage: "a.circle") }

            Button("Switch to tab A") {
                activeTabIndex = 0
            }
            .tag(1)
            .tabItem { Label("Tab B", systemImage: "b.circle") }
        }
    }
}

但真正好的地方是,在識別和切換標簽時,我們并不僅僅局限于使用整數(shù)。相反,我們可以自由地使用任何Hashable值來表示每個標簽——例如通過使用一個枚舉,其中包含我們想要顯示的每個標簽的情況。然后我們可以將這部分狀態(tài)封裝在一個ObservableObject中,這樣我們就可以很容易地注入到我們的視圖層次環(huán)境中:

enum Tab {
    case home
    case search
    case settings
}
class TabController: ObservableObject {
    @Published var activeTab = Tab.home
    func open(_ tab: Tab) {
        activeTab = tab
    }
}

有了上述內(nèi)容,我們現(xiàn)在可以用新的Tab類型來標記TabView中的每個視圖,如果我們再把TabController注入到視圖層次結(jié)構(gòu)的環(huán)境中,那么其中的任何視圖都可以隨時切換顯示的Tab。

struct RootView: View {
    @StateObject private var tabController = TabController()
    var body: some View {
        TabView(selection: $tabController.activeTab) {
            HomeView()
                .tag(Tab.home)
                .tabItem { Label("Home", systemImage: "house") }
            SearchView()
                .tag(Tab.search)
                .tabItem { Label("Search", systemImage: "magnifyingglass") }
            SettingsView()
                .tag(Tab.settings)
                .tabItem { Label("Settings", systemImage: "gearshape") }
        }
        .environmentObject(tabController)
    }
}

例如,現(xiàn)在我們的HomeView可以使用一個完全自定義的按鈕切換到設(shè)置標簽——它只需要從環(huán)境中獲取我們的TabController,然后它可以調(diào)用open方法來執(zhí)行標簽切換,像這樣:

struct HomeView: View {
    @EnvironmentObject private var tabController: TabController
    var body: some View {
        ScrollView {
            ...
            Button("Open settings") {
                tabController.open(.settings)
            }
        }
    }
}

很好! 另外,由于TabController是一個完全由我們控制的對象,我們也可以用它來切換主視圖層次結(jié)構(gòu)以外的標簽。例如,我們可能想根據(jù)推送通知或其他類型的服務(wù)器事件來切換標簽,現(xiàn)在可以通過調(diào)用上述視圖代碼中的相同的open方法來完成。

要了解更多關(guān)于環(huán)境對象以及SwiftUI狀態(tài)管理系統(tǒng)的其余部分,請查看本指南。

控制導(dǎo)航堆棧

就像標簽視圖一樣,SwiftUI的NavigationView也可以被編程自定義控制。例如,假設(shè)我們正在開發(fā)一個應(yīng)用程序,在其主導(dǎo)航堆棧中顯示一個日歷視圖作為根視圖,然后用戶可以通過點擊位于該應(yīng)用程序?qū)Ш綑谥械木庉嫲粹o來打開一個日歷編輯視圖。為了連接這兩個視圖,我們使用了一個NavigationLink,每當點擊一個給定的視圖時,它就會自動將其壓入到導(dǎo)航棧中:

struct RootView: View {
    @ObservedObject var calendarController: CalendarController
    var body: some View {
        NavigationView {
            CalendarView(
                calendar: calendarController.calendar
            )
            .toolbar {
                ToolbarItem(placement: .navigationBarTrailing) {
                    NavigationLink("Edit") {
              CalendarEditView(
                  calendar: $calendarController.calendar
              )
              .navigationTitle("Edit your calendar")
          }
                }
            }
            .navigationTitle("Your calendar")
        }
        .navigationViewStyle(.stack)
    }
}

在這種情況下,我們在所有設(shè)備上使用堆棧式導(dǎo)航風(fēng)格,甚至是iPad,而不是讓系統(tǒng)選擇使用哪種導(dǎo)航風(fēng)格。

現(xiàn)在我們假設(shè),我們想讓我們的CalendarView以自定義方式顯示其編輯視圖,而不需要構(gòu)建一個單獨的實例。要做到這一點,我們可以在編輯按鈕的NavigationLink中注入一個isActive綁定,然后將其傳遞給我們的CalendarView:

struct RootView: View {
    @ObservedObject var calendarController: CalendarController
    @State private var isEditViewShown = false
    var body: some View {
        NavigationView {
            CalendarView(
                calendar: calendarController.calendar,
                isEditViewShown: $isEditViewShown
            )
            .toolbar {
                ToolbarItem(placement: .navigationBarTrailing) {
                    NavigationLink("Edit", isActive: $isEditViewShown) {
                        CalendarEditView(
                            calendar: $calendarController.calendar
                        )
                        .navigationTitle("Edit your calendar")
                    }
                }
            }
            .navigationTitle("Your calendar")
        }
        .navigationViewStyle(.stack)
    }
}

如果我們現(xiàn)在也更新CalendarView,使其使用@Binding綁定屬性接受上述值,那么現(xiàn)在只要我們想顯示我們的編輯視圖,就可以簡單地將該屬性設(shè)置為true,我們的根視圖的NavigationLink將自動被觸發(fā):

struct CalendarView: View {
    var calendar: Calendar
    @Binding var isEditViewShown: Bool
    var body: some View {
        ScrollView {
            ...
            Button("Edit calendar settings") {
                isEditViewShown = true
            }
        }
    }
}

當然,我們也可以選擇將isEditViewShown屬性封裝在某種形式的ObservableObject中,例如NavigationController,就像我們之前處理TabView時那樣。

這就是我們?nèi)绾我宰远x編程方式觸發(fā)顯示在我們的用戶界面中的NavigationLink——但如果我們想在不給用戶任何直接控制的情況下執(zhí)行這種導(dǎo)航呢?

例如,我們現(xiàn)在假設(shè)我們正在開發(fā)一個包括導(dǎo)出功能的視頻編輯應(yīng)用程序。當用戶進入導(dǎo)出流程時,一個VideoExportView被顯示為模態(tài),一旦導(dǎo)出操作完成,我們想把VideoExportFinishedView推送到該模態(tài)的導(dǎo)航棧中。

最初,這可能看起來非常棘手,因為(由于SwiftUI是一個聲明式的UI框架)沒有push方法,當我們想在導(dǎo)航棧中添加一個新視圖時,我們可以調(diào)用該方法。事實上,在NavigationView中顯示一個新視圖的唯一內(nèi)置方法是使用NavigationLink,它需要成為我們視圖層次結(jié)構(gòu)本身的一部分。

也就是說,這些NavigationLink實際上不一定是可見的——所以在這種情況下,實現(xiàn)我們目標的一個方法是在我們的視圖中添加一個隱藏的導(dǎo)航鏈接,然后我們可以在視頻導(dǎo)出操作完成后以編程方式觸發(fā)該鏈接。如果我們也在我們的目標視圖中隱藏系統(tǒng)提供的返回按鈕,那么我們就可以完全鎖定用戶能夠在這兩個視圖之間手動導(dǎo)航:

struct VideoExportView: View {
    @ObservedObject var exporter: VideoExporter
    @State private var didFinish = false
    @Environment(\.presentationMode) private var presentationMode
    var body: some View {
        NavigationView {
            VStack {
                ...
                Button("Export") {
                    exporter.export {
    didFinish = true
}
                }
                .disabled(exporter.isExporting)

                NavigationLink("Hidden finish link", isActive: $didFinish) {
                    VideoExportFinishedView(doneAction: {
                        presentationMode.wrappedValue.dismiss()
                    })
                    .navigationTitle("Export completed")
                    .navigationBarBackButtonHidden(true)
                }
                .hidden()
            }
            .navigationTitle("Export this video")
        }
        .navigationViewStyle(.stack)
    }
}
struct VideoExportFinishedView: View {
    var doneAction: () -> Void

    var body: some View {
        VStack {
            Label("Your video was exported", systemImage: "checkmark.circle")
            ...
            Button("Done", action: doneAction)
        }
    }
}

我們在VideoExportFinishedView中注入一個doedAction閉包,而不是讓它檢索當前的presentationMode本身,是因為我們希望解耦整個模態(tài)流程,而不僅僅是那個特定的視圖。要了解更多信息,請查看 "解耦SwiftUI模態(tài)或詳細視圖"。

使用這樣一個隱藏的NavigationLink絕對可以被認為是一個有點 "黑 "的解決方案,但它的效果非常好,如果我們把一個導(dǎo)航鏈接看成是導(dǎo)航堆棧中兩個視圖之間的連接(而不僅僅是一個按鈕),那么上述設(shè)置可以說是有意義的。

關(guān)于“SwiftUI怎么自定義導(dǎo)航”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識,可以關(guān)注億速云行業(yè)資訊頻道,小編每天都會為大家更新不同的知識點。

向AI問一下細節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI