SwiftUI自定義導(dǎo)航的方法實例
前言
默認(rèn)情況下,SwiftUI提供的各種導(dǎo)航API在很大程度上是以用戶直接輸入為中心的——也就是說,導(dǎo)航是在系統(tǒng)響應(yīng)例如按鈕的點擊和標(biāo)簽切換等事件時由系統(tǒng)本身處理的。
然而,有時我們可能想更直接地控制應(yīng)用程序的導(dǎo)航執(zhí)行方式,盡管SwiftUI在這方面仍然不如UIKit或AppKit靈活,但它確實提供了相當(dāng)多的方法,讓我們在構(gòu)建的視圖中執(zhí)行完全自定義的導(dǎo)航。
切換標(biāo)簽(tabs)
讓我們先來看看我們?nèi)绾文芸刂飘?dāng)前在??TabView???中顯示的標(biāo)簽。通常情況下,當(dāng)用戶手動點擊每個標(biāo)簽欄中的一個項目時,標(biāo)簽就會被切換,但是通過在一個給定的??TabView???中注入一個選擇(??selection???)綁定,我們可以觀察并控制當(dāng)前顯示的標(biāo)簽。在這里,我們要做的就是在兩個標(biāo)簽之間切換,這兩個標(biāo)簽是用整數(shù)??0???和??1??標(biāo)記的:復(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") } ? ? ? ? } ? ? } }
但真正好的地方是,在識別和切換標(biāo)簽時,我們并不僅僅局限于使用整數(shù)。相反,我們可以自由地使用任何??Hashable???值來表示每個標(biāo)簽——例如通過使用一個枚舉,其中包含我們想要顯示的每個標(biāo)簽的情況。然后我們可以將這部分狀態(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???類型來標(biāo)記??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è)置標(biāo)簽——它只需要從環(huán)境中獲取我們的??TabController???,然后它可以調(diào)用??open??方法來執(zhí)行標(biāo)簽切換,像這樣:
struct HomeView: View { ? ? @EnvironmentObject private var tabController: TabController ? ? var body: some View { ? ? ? ? ScrollView { ? ? ? ? ? ? ... ? ? ? ? ? ? Button("Open settings") { ? ? ? ? ? ? ? ? tabController.open(.settings) ? ? ? ? ? ? } ? ? ? ? } ? ? } }
很好! 另外,由于??TabController???是一個完全由我們控制的對象,我們也可以用它來切換主視圖層次結(jié)構(gòu)以外的標(biāo)簽。例如,我們可能想根據(jù)推送通知或其他類型的服務(wù)器事件來切換標(biāo)簽,現(xiàn)在可以通過調(diào)用上述視圖代碼中的相同的??open??方法來完成。
要了解更多關(guān)于環(huán)境對象以及SwiftUI狀態(tài)管理系統(tǒng)的其余部分,請查看本指南。
控制導(dǎo)航堆棧
就像標(biāo)簽視圖一樣,SwiftUI的??NavigationView???也可以被編程自定義控制。例如,假設(shè)我們正在開發(fā)一個應(yīng)用程序,在其主導(dǎo)航堆棧中顯示一個日歷視圖作為根視圖,然后用戶可以通過點擊位于該應(yīng)用程序?qū)Ш綑谥械木庉嫲粹o來打開一個日歷編輯視圖。為了連接這兩個視圖,我們使用了一個??NavigationLink??,每當(dāng)點擊一個給定的視圖時,它就會自動將其壓入到導(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 } } } }
當(dāng)然,我們也可以選擇將??isEditViewShown???屬性封裝在某種形式的??ObservableObject???中,例如??NavigationController???,就像我們之前處理??TabView??時那樣。
這就是我們?nèi)绾我宰远x編程方式觸發(fā)顯示在我們的用戶界面中的??NavigationLink??——但如果我們想在不給用戶任何直接控制的情況下執(zhí)行這種導(dǎo)航呢?
例如,我們現(xiàn)在假設(shè)我們正在開發(fā)一個包括導(dǎo)出功能的視頻編輯應(yīng)用程序。當(dāng)用戶進(jìn)入導(dǎo)出流程時,一個??VideoExportView???被顯示為模態(tài),一旦導(dǎo)出操作完成,我們想把??VideoExportFinishedView??推送到該模態(tài)的導(dǎo)航棧中。
最初,這可能看起來非常棘手,因為(由于SwiftUI是一個聲明式的UI框架)沒有??push???方法,當(dāng)我們想在導(dǎo)航棧中添加一個新視圖時,我們可以調(diào)用該方法。事實上,在??NavigationView???中顯示一個新視圖的唯一內(nèi)置方法是使用??NavigationLink??,它需要成為我們視圖層次結(jié)構(gòu)本身的一部分。
也就是說,這些??NavigationLink??實際上不一定是可見的——所以在這種情況下,實現(xiàn)我們目標(biāo)的一個方法是在我們的視圖中添加一個隱藏的導(dǎo)航鏈接,然后我們可以在視頻導(dǎo)出操作完成后以編程方式觸發(fā)該鏈接。如果我們也在我們的目標(biāo)視圖中隱藏系統(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???閉包,而不是讓它檢索當(dāng)前的??presentationMode??本身,是因為我們希望解耦整個模態(tài)流程,而不僅僅是那個特定的視圖。要了解更多信息,請查看 "解耦SwiftUI模態(tài)或詳細(xì)視圖"。
使用這樣一個隱藏的??NavigationLink??絕對可以被認(rèn)為是一個有點 "黑 "的解決方案,但它的效果非常好,如果我們把一個導(dǎo)航鏈接看成是導(dǎo)航堆棧中兩個視圖之間的連接(而不僅僅是一個按鈕),那么上述設(shè)置可以說是有意義的。
小結(jié)
盡管SwiftUI的導(dǎo)航系統(tǒng)仍然不如UIKit和AppKit提供的系統(tǒng)靈活,但它已經(jīng)足夠強大,可以滿足很多不同的使用情——-特別是當(dāng)與SwiftUI非常全面的狀態(tài)管理系統(tǒng)相結(jié)合時。
當(dāng)然,我們也可以選擇將我們的SwiftUI視圖層次包裹在托管控制器中,只使用UIKit/AppKit來實現(xiàn)我們的導(dǎo)航代碼。哪種解決方案是最合適的,可能取決于我們在每個項目中實際想要執(zhí)行多少自定義和程序化的導(dǎo)航。
到此這篇關(guān)于SwiftUI自定義導(dǎo)航的文章就介紹到這了,更多相關(guān)SwiftUI自定義導(dǎo)航內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Swift map和filter函數(shù)原型基礎(chǔ)示例
這篇文章主要為大家介紹了Swift map和filter函數(shù)原型基礎(chǔ)示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-07-07Swift中內(nèi)置的集合類型學(xué)習(xí)筆記
Swift中自帶數(shù)組、set、字典三大集合類型,這里將學(xué)習(xí)過程中的基礎(chǔ)的Swift中內(nèi)置的集合類型學(xué)習(xí)筆記進(jìn)行整理,需要的朋友可以參考下2016-06-06使用Swift實現(xiàn)iOScollectionView廣告無限滾動效果(DEMO)
本文給大家分享使用Swift實現(xiàn)iOScollectionView廣告無限滾動效果(DEMO),非常不錯,具有一定的參考借鑒價值,感興趣的朋友一起看看吧2016-11-11