您好,登錄后才能下訂單哦!
這篇文章主要介紹“SwiftUI怎么自定義導航”的相關知識,小編通過實際案例向大家展示操作過程,操作方法簡單快捷,實用性強,希望這篇“SwiftUI怎么自定義導航”文章能幫助大家解決問題。
默認情況下,SwiftUI提供的各種導航API在很大程度上是以用戶直接輸入為中心的——也就是說,導航是在系統響應例如按鈕的點擊和標簽切換等事件時由系統本身處理的。
然而,有時我們可能想更直接地控制應用程序的導航執行方式,盡管SwiftUI在這方面仍然不如UIKit或AppKit靈活,但它確實提供了相當多的方法,讓我們在構建的視圖中執行完全自定義的導航。
讓我們先來看看我們如何能控制當前在TabView中顯示的標簽。通常情況下,當用戶手動點擊每個標簽欄中的一個項目時,標簽就會被切換,但是通過在一個給定的TabView中注入一個選擇(selection)綁定,我們可以觀察并控制當前顯示的標簽。在這里,我們要做的就是在兩個標簽之間切換,這兩個標簽是用整數0和1標記的:復制
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") } } } }
但真正好的地方是,在識別和切換標簽時,我們并不僅僅局限于使用整數。相反,我們可以自由地使用任何Hashable值來表示每個標簽——例如通過使用一個枚舉,其中包含我們想要顯示的每個標簽的情況。然后我們可以將這部分狀態封裝在一個ObservableObject中,這樣我們就可以很容易地注入到我們的視圖層次環境中:
enum Tab { case home case search case settings } class TabController: ObservableObject { @Published var activeTab = Tab.home func open(_ tab: Tab) { activeTab = tab } }
有了上述內容,我們現在可以用新的Tab類型來標記TabView中的每個視圖,如果我們再把TabController注入到視圖層次結構的環境中,那么其中的任何視圖都可以隨時切換顯示的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) } }
例如,現在我們的HomeView可以使用一個完全自定義的按鈕切換到設置標簽——它只需要從環境中獲取我們的TabController,然后它可以調用open方法來執行標簽切換,像這樣:
struct HomeView: View { @EnvironmentObject private var tabController: TabController var body: some View { ScrollView { ... Button("Open settings") { tabController.open(.settings) } } } }
很好! 另外,由于TabController是一個完全由我們控制的對象,我們也可以用它來切換主視圖層次結構以外的標簽。例如,我們可能想根據推送通知或其他類型的服務器事件來切換標簽,現在可以通過調用上述視圖代碼中的相同的open方法來完成。
要了解更多關于環境對象以及SwiftUI狀態管理系統的其余部分,請查看本指南。
就像標簽視圖一樣,SwiftUI的NavigationView也可以被編程自定義控制。例如,假設我們正在開發一個應用程序,在其主導航堆棧中顯示一個日歷視圖作為根視圖,然后用戶可以通過點擊位于該應用程序導航欄中的編輯按鈕來打開一個日歷編輯視圖。為了連接這兩個視圖,我們使用了一個NavigationLink,每當點擊一個給定的視圖時,它就會自動將其壓入到導航棧中:
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) } }
在這種情況下,我們在所有設備上使用堆棧式導航風格,甚至是iPad,而不是讓系統選擇使用哪種導航風格。
現在我們假設,我們想讓我們的CalendarView以自定義方式顯示其編輯視圖,而不需要構建一個單獨的實例。要做到這一點,我們可以在編輯按鈕的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) } }
如果我們現在也更新CalendarView,使其使用@Binding綁定屬性接受上述值,那么現在只要我們想顯示我們的編輯視圖,就可以簡單地將該屬性設置為true,我們的根視圖的NavigationLink將自動被觸發:
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時那樣。
這就是我們如何以自定義編程方式觸發顯示在我們的用戶界面中的NavigationLink——但如果我們想在不給用戶任何直接控制的情況下執行這種導航呢?
例如,我們現在假設我們正在開發一個包括導出功能的視頻編輯應用程序。當用戶進入導出流程時,一個VideoExportView被顯示為模態,一旦導出操作完成,我們想把VideoExportFinishedView推送到該模態的導航棧中。
最初,這可能看起來非常棘手,因為(由于SwiftUI是一個聲明式的UI框架)沒有push方法,當我們想在導航棧中添加一個新視圖時,我們可以調用該方法。事實上,在NavigationView中顯示一個新視圖的唯一內置方法是使用NavigationLink,它需要成為我們視圖層次結構本身的一部分。
也就是說,這些NavigationLink實際上不一定是可見的——所以在這種情況下,實現我們目標的一個方法是在我們的視圖中添加一個隱藏的導航鏈接,然后我們可以在視頻導出操作完成后以編程方式觸發該鏈接。如果我們也在我們的目標視圖中隱藏系統提供的返回按鈕,那么我們就可以完全鎖定用戶能夠在這兩個視圖之間手動導航:
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本身,是因為我們希望解耦整個模態流程,而不僅僅是那個特定的視圖。要了解更多信息,請查看 "解耦SwiftUI模態或詳細視圖"。
使用這樣一個隱藏的NavigationLink絕對可以被認為是一個有點 "黑 "的解決方案,但它的效果非常好,如果我們把一個導航鏈接看成是導航堆棧中兩個視圖之間的連接(而不僅僅是一個按鈕),那么上述設置可以說是有意義的。
關于“SwiftUI怎么自定義導航”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識,可以關注億速云行業資訊頻道,小編每天都會為大家更新不同的知識點。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。