SwiftUI自定義導(dǎo)航的方法實(shí)例
前言
默認(rèn)情況下,SwiftUI提供的各種導(dǎo)航API在很大程度上是以用戶直接輸入為中心的——也就是說,導(dǎo)航是在系統(tǒng)響應(yīng)例如按鈕的點(diǎn)擊和標(biāo)簽切換等事件時(shí)由系統(tǒng)本身處理的。
然而,有時(shí)我們可能想更直接地控制應(yīng)用程序的導(dǎo)航執(zhí)行方式,盡管SwiftUI在這方面仍然不如UIKit或AppKit靈活,但它確實(shí)提供了相當(dāng)多的方法,讓我們?cè)跇?gòu)建的視圖中執(zhí)行完全自定義的導(dǎo)航。
切換標(biāo)簽(tabs)
讓我們先來看看我們?nèi)绾文芸刂飘?dāng)前在??TabView???中顯示的標(biāo)簽。通常情況下,當(dāng)用戶手動(dòng)點(diǎn)擊每個(gè)標(biāo)簽欄中的一個(gè)項(xiàng)目時(shí),標(biāo)簽就會(huì)被切換,但是通過在一個(gè)給定的??TabView???中注入一個(gè)選擇(??selection???)綁定,我們可以觀察并控制當(dāng)前顯示的標(biāo)簽。在這里,我們要做的就是在兩個(gè)標(biāo)簽之間切換,這兩個(gè)標(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") } ? ? ? ? } ? ? } }
但真正好的地方是,在識(shí)別和切換標(biāo)簽時(shí),我們并不僅僅局限于使用整數(shù)。相反,我們可以自由地使用任何??Hashable???值來表示每個(gè)標(biāo)簽——例如通過使用一個(gè)枚舉,其中包含我們想要顯示的每個(gè)標(biāo)簽的情況。然后我們可以將這部分狀態(tài)封裝在一個(gè)??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???中的每個(gè)視圖,如果我們?cè)侔??TabController??注入到視圖層次結(jié)構(gòu)的環(huán)境中,那么其中的任何視圖都可以隨時(shí)切換顯示的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???可以使用一個(gè)完全自定義的按鈕切換到設(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???是一個(gè)完全由我們控制的對(duì)象,我們也可以用它來切換主視圖層次結(jié)構(gòu)以外的標(biāo)簽。例如,我們可能想根據(jù)推送通知或其他類型的服務(wù)器事件來切換標(biāo)簽,現(xiàn)在可以通過調(diào)用上述視圖代碼中的相同的??open??方法來完成。
要了解更多關(guān)于環(huán)境對(duì)象以及SwiftUI狀態(tài)管理系統(tǒng)的其余部分,請(qǐng)查看本指南。
控制導(dǎo)航堆棧
就像標(biāo)簽視圖一樣,SwiftUI的??NavigationView???也可以被編程自定義控制。例如,假設(shè)我們正在開發(fā)一個(gè)應(yīng)用程序,在其主導(dǎo)航堆棧中顯示一個(gè)日歷視圖作為根視圖,然后用戶可以通過點(diǎn)擊位于該應(yīng)用程序?qū)Ш綑谥械木庉嫲粹o來打開一個(gè)日歷編輯視圖。為了連接這兩個(gè)視圖,我們使用了一個(gè)??NavigationLink??,每當(dāng)點(diǎn)擊一個(gè)給定的視圖時(shí),它就會(huì)自動(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) ? ? } }
在這種情況下,我們?cè)谒性O(shè)備上使用堆棧式導(dǎo)航風(fēng)格,甚至是iPad,而不是讓系統(tǒng)選擇使用哪種導(dǎo)航風(fēng)格。
現(xiàn)在我們假設(shè),我們想讓我們的??CalendarView???以自定義方式顯示其編輯視圖,而不需要構(gòu)建一個(gè)單獨(dú)的實(shí)例。要做到這一點(diǎn),我們可以在編輯按鈕的??NavigationLink???中注入一個(gè)??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)在只要我們想顯示我們的編輯視圖,就可以簡(jiǎn)單地將該屬性設(shè)置為??true???,我們的根視圖的??NavigationLink??將自動(dòng)被觸發(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??時(shí)那樣。
這就是我們?nèi)绾我宰远x編程方式觸發(fā)顯示在我們的用戶界面中的??NavigationLink??——但如果我們想在不給用戶任何直接控制的情況下執(zhí)行這種導(dǎo)航呢?
例如,我們現(xiàn)在假設(shè)我們正在開發(fā)一個(gè)包括導(dǎo)出功能的視頻編輯應(yīng)用程序。當(dāng)用戶進(jìn)入導(dǎo)出流程時(shí),一個(gè)??VideoExportView???被顯示為模態(tài),一旦導(dǎo)出操作完成,我們想把??VideoExportFinishedView??推送到該模態(tài)的導(dǎo)航棧中。
最初,這可能看起來非常棘手,因?yàn)椋ㄓ捎赟wiftUI是一個(gè)聲明式的UI框架)沒有??push???方法,當(dāng)我們想在導(dǎo)航棧中添加一個(gè)新視圖時(shí),我們可以調(diào)用該方法。事實(shí)上,在??NavigationView???中顯示一個(gè)新視圖的唯一內(nèi)置方法是使用??NavigationLink??,它需要成為我們視圖層次結(jié)構(gòu)本身的一部分。
也就是說,這些??NavigationLink??實(shí)際上不一定是可見的——所以在這種情況下,實(shí)現(xiàn)我們目標(biāo)的一個(gè)方法是在我們的視圖中添加一個(gè)隱藏的導(dǎo)航鏈接,然后我們可以在視頻導(dǎo)出操作完成后以編程方式觸發(fā)該鏈接。如果我們也在我們的目標(biāo)視圖中隱藏系統(tǒng)提供的返回按鈕,那么我們就可以完全鎖定用戶能夠在這兩個(gè)視圖之間手動(dò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) ? ? ? ? } ? ? } }
我們?cè)??VideoExportFinishedView???中注入一個(gè)??doedAction???閉包,而不是讓它檢索當(dāng)前的??presentationMode??本身,是因?yàn)槲覀兿M怦钫麄€(gè)模態(tài)流程,而不僅僅是那個(gè)特定的視圖。要了解更多信息,請(qǐng)查看 "解耦SwiftUI模態(tài)或詳細(xì)視圖"。
使用這樣一個(gè)隱藏的??NavigationLink??絕對(duì)可以被認(rèn)為是一個(gè)有點(diǎn) "黑 "的解決方案,但它的效果非常好,如果我們把一個(gè)導(dǎo)航鏈接看成是導(dǎo)航堆棧中兩個(gè)視圖之間的連接(而不僅僅是一個(gè)按鈕),那么上述設(shè)置可以說是有意義的。
小結(jié)
盡管SwiftUI的導(dǎo)航系統(tǒng)仍然不如UIKit和AppKit提供的系統(tǒng)靈活,但它已經(jīng)足夠強(qiáng)大,可以滿足很多不同的使用情——-特別是當(dāng)與SwiftUI非常全面的狀態(tài)管理系統(tǒng)相結(jié)合時(shí)。
當(dāng)然,我們也可以選擇將我們的SwiftUI視圖層次包裹在托管控制器中,只使用UIKit/AppKit來實(shí)現(xiàn)我們的導(dǎo)航代碼。哪種解決方案是最合適的,可能取決于我們?cè)诿總€(gè)項(xiàng)目中實(shí)際想要執(zhí)行多少自定義和程序化的導(dǎo)航。
到此這篇關(guān)于SwiftUI自定義導(dǎo)航的文章就介紹到這了,更多相關(guān)SwiftUI自定義導(dǎo)航內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳談swift內(nèi)存管理中的引用計(jì)數(shù)
下面小編就為大家?guī)硪黄斦剆wift內(nèi)存管理中的引用計(jì)數(shù)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-09-09Swift 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實(shí)現(xiàn)iOScollectionView廣告無限滾動(dòng)效果(DEMO)
本文給大家分享使用Swift實(shí)現(xiàn)iOScollectionView廣告無限滾動(dòng)效果(DEMO),非常不錯(cuò),具有一定的參考借鑒價(jià)值,感興趣的朋友一起看看吧2016-11-11Swift算法之棧和隊(duì)列的實(shí)現(xiàn)方法示例
Swift語(yǔ)言中沒有內(nèi)設(shè)的棧和隊(duì)列,很多擴(kuò)展庫(kù)中使用Generic Type來實(shí)現(xiàn)?;蚴顷?duì)列。下面這篇文章就來給大家詳細(xì)介紹了Swift算法之棧和隊(duì)列的實(shí)現(xiàn)方法,需要的朋友可以參考學(xué)習(xí),下面來一起看看吧。2017-03-03IOS 實(shí)現(xiàn)簡(jiǎn)單的彈幕功能
本文主要介紹IOS 實(shí)現(xiàn)彈幕功能,這里給大家一個(gè)實(shí)例來展現(xiàn)彈幕功能,有需要的小伙伴可以參考下2016-07-07