SwiftUI 開發之旅:自定義 TabView
theme: smartblue
開啟掘金成長之旅!這是我參與「掘金日新計劃 · 12 月更文挑戰」的第6天,點選檢視活動詳情
好久不見,我是 new_cheng。
關於自定義 TabView,首先要明白,為什麼不使用官方的 TabView,為什麼要自定義一個 TabView?
有幾個值得這麼做的理由:
- 更靈活的控制 TabView 的顯示;
- 高度的定製化,比如給 TabView 設定面板(誰能不愛好看的面板呢?);
以上 2 點就值得你自定義一個 TabView。
話不多說,開搞。
實現思路
一個標準的 TabView, 先來看看完成圖:
實現思路也很簡單:
- 建立一個路由控制器;
- 建立一個 TabBarIcon;
- 自定義檢視
- 檢測路由變化,切換檢視;
建立路由控制器
這裡的路由只是一個稱呼,和前端領域的路由不同。
ViewRouter.swift:
```swift import SwiftUI
enum Page { case home case my }
class ViewRouter: ObservableObject { @Published var currentPage: Page = .home } ```
ViewRouter 是一個遵循 ObservableObject
協議的類,它的 currentPage
屬性是用 @Published
進行包裝的。當屬性值發生變化時,使用該類的任何檢視都會自動重新呼叫 body
屬性,來保持介面與資料的一致性。
建立 TabBarIcon
接著,建立 TabView 的選單內容,我們得封裝一個 TabBarIcon。
在 ViewRouter.swift 檔案中新增以下程式碼:
```swift struct TabBarIcon: View {
@StateObject var viewRouter: ViewRouter
let assignedPage: Page
let width, height: CGFloat
let systemIconName, tabName: String
var body: some View { VStack { Image(systemName: systemIconName) .resizable() .aspectRatio(contentMode: .fit) .frame(width: width, height: height) .padding(.top, 6) Text(tabName) .font(.footnote) .font(.system(size: 16)) Spacer() } .padding(.horizontal, -2) .onTapGesture { viewRouter.currentPage = assignedPage } .foregroundColor(viewRouter.currentPage == assignedPage ? .blue : .gray) } } ```
當用戶點選的時候,會去更新當前路由;值得一提的是,這裡我們採用的是 @StateObject
,而不是 @ObservedObject
,@ObservedObject 不管儲存,會隨著檢視的建立被多次建立。而 @StateObject 保證物件只會被建立一次。因此,如果是在視圖裡自行建立的 ObservableObject model 物件,使用 @StateObject 會是更正確的選擇。
自定義檢視
為了顯示路由檢視,我們還需要自定義檢視。藉助 GeometryReader
,我們可以很輕鬆的做到這一點。GemoetryReader 是一個容器檢視,能夠根據其自身大小和座標空間定義其內容,簡單來說,GeometryReader 是一種特別的 View,在其中可以拿到一些你在其他 View 中拿不到的資訊,比如父級檢視的 size。
ContentView.siwft: ```swift struct ContentView: View {
var body: some View {
GeometryReader { geometry in
VStack {
Spacer()
ZStack {
HStack {
TabBarIcon(viewRouter: viewRouter, assignedPage: .home,
width: geometry.size.width/5, height: geometry.size.height/32,
systemIconName: "chart.pie.fill", tabName: "首頁")
.frame(maxWidth: .infinity)
TabBarIcon(viewRouter: viewRouter, assignedPage: .my,
width: geometry.size.width/5, height: geometry.size.height/32,
systemIconName: "person.crop.circle.fill", tabName: "我的")
.frame(maxWidth: .infinity)
}
// 將寬度設定為父檢視的寬度大小,高度需要微調,可以設定為具體是數值,比如 100
.frame(width: geometry.size.width, height: geometry.size.height/9)
}
}
.ignoresSafeArea(edges: .bottom)
}
}
} ```
檢測路由變化,切換檢視
用 switch
來切換檢視:
```swift struct ContentView: View { @StateObject var viewRouter: ViewRouter
var body: some View {
GeometryReader { geometry in
VStack {
switch viewRouter.currentPage {
case .home:
Home()
case .my:
My()
}
Spacer()
ZStack {
HStack {
TabBarIcon(viewRouter: viewRouter, assignedPage: .home,
width: geometry.size.width/5, height: geometry.size.height/32,
systemIconName: "chart.pie.fill", tabName: "首頁")
.frame(maxWidth: .infinity)
TabBarIcon(viewRouter: viewRouter, assignedPage: .my,
width: geometry.size.width/5, height: geometry.size.height/32,
systemIconName: "person.crop.circle.fill", tabName: "我的")
.frame(maxWidth: .infinity)
}
// 將寬度設定為父檢視的寬度大小,高度需要微調,可以設定為具體是數值,比如 100
.frame(width: geometry.size.width, height: geometry.size.height/9)
}
}
.ignoresSafeArea(edges: .bottom)
}
}
}
// 設定預覽 struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView(viewRouter: ViewRouter()) } } ```
到這裡,一個自定義 TabView 就完成了。
面板
我們的 TabView 既然都是完全自定義的了,那給它開發面板自然不在話下了;像招行的 APP 就有很多漂亮的面板,這對提高使用者粘性來說,是一個不錯的方式(含淚給王者農藥打錢😭)。當然這完全是由你或者設計師來決定的,按照設計稿開搞就是了。
關於怎麼做 SwiftUI 的面板定製,先給自己挖個坑,以後來填上😋。
額外收穫
當使用 swiftui 的 NavigationView
進行導航時,如果你在一個父級檢視中使用了 NavigationView,然後在子級檢視中也使用了 NavigationView,那在進行導航的時候,就有可能出現 2 個導航欄的情況。
這時候的解決辦法是就是僅在頂級父檢視,也就是 ContentView.swift 中使用 NavigationView:
```swift struct ContentView: View { @StateObject var viewRouter: ViewRouter
var body: some View {
NavigationView {
// ...
}
}
} ```
此時,如果你使用的是 swiftui 自帶的 TabView 檢視,而非自定義的,而又想在子檢視中不顯示 TabView,這會很麻煩,需要藉助第三方庫:SwiftUI-Introspect。
到這,還沒有完,用 SwiftUI-Introspect 控制官方 TabView 的顯示,顯示和隱藏的時候,會有一個過渡動畫...
如果你不介意在每次從子檢視返回首頁的時候,TabView 顯示的這個過度動畫帶來的延遲效果,那就可以採用官方的 TabView。
而當你使用自定義的 TabView 時,這些問題都迎刃而解💯。
總結
我們用簡單的程式碼就實現了一個自定義的 TabView,通過她,我們能做到高度的自定義效果,值得一試!
這是 SwiftUI 開發之旅專欄的文章,是 swiftui 開發學習的經驗總結及實用技巧分享,歡迎關注該專欄,會堅持輸出。同時歡迎關注我的個人公眾號 @JSHub:提供最新的開發資訊速報,優質的技術乾貨推薦。或是檢視我的個人部落格:Devcursor。
👍點贊:如果有收穫和幫助,請點個贊支援一下!
🌟收藏:歡迎收藏文章,隨時檢視!
💬評論:歡迎評論交流學習,共同進步!