SwiftUI 框架

在 SwiftUI 構建可滾動的客製化標籤列 大大提升使用者體驗

很多流行的手機 App 都用到 Tab Bar,讓使用者可以快速和方便地切換到 App 的不同功能,大大提升使用者體驗。在這篇文章中,我會帶大家使用 SwiftUI 的 TabView,輕鬆地客製化一個可滾動的 Tab Bar,並添加漂亮的動畫,來滿足你的 App 的需要。
在 SwiftUI 構建可滾動的客製化標籤列 大大提升使用者體驗
Photo by Erik Mclean on Unsplash
在 SwiftUI 構建可滾動的客製化標籤列 大大提升使用者體驗
Photo by Erik Mclean on Unsplash
In: SwiftUI 框架

無論是在構建社交媒體 App 或生產力工具 (productivity tool) 時,我們都可以利用標籤列 (Tab Bar) 介面讓它更加直觀和易於使用,提升使用者體驗。現在有了 SwiftUI 的 TabView,要創建無縫 (seamless)、而且可以客製化的標籤介面 (tab interface) 十分簡單。

在預設情況下,iOS 會以標準形式顯示標籤列,方便使用者可以快速切換不同 App。但是,開發者可能會想客製化標籤列,來滿足 App 的特定需求。

在這篇教學文章中,我會帶大家利用 SwiftUI 構建一個可滾動的動畫標籤列,並支援無限的標籤項目 (tab item)。完成這篇教學之後,我們會實作出以下的標籤列:

swiftui-tabbar-final

標籤視圖 (Tab View) 和標籤列簡介

如果你還沒有用過 TabView,可以先看看以下的簡介。要創建一個標籤視圖,我們只需要使用 TabView,並在當中嵌入子視圖。我們可以應用 tabItem 修飾符,指定每個子視圖的項目描述。讓我們看看以下例子:

struct ContentView: View {
    let colors: [Color] = [ .yellow, .blue, .green, .indigo, .brown ]
    let tabbarItems = [ "Random", "Travel", "Wallpaper", "Food", "Interior Design" ]

    var body: some View {
        TabView {
            ForEach(colors.indices, id: \.self) { index in
                colors[index]
                    .frame(maxWidth: .infinity, maxHeight: .infinity)
                    .tag(index)
                    .tabItem {
                        Image(systemName: "\(index + 1).circle")
                        Text(tabbarItems[index])
                    }
            }
        }
    }
}

以上的程式碼會創建出一個簡單的標籤視圖,當中有 5 個標籤項目。我們用了 Image 來顯示標籤圖示 (icon)。如果我們在 Xcode 編寫以上的程式碼,應該會在預覽中看到以下的標籤列:

Sample tab bar using SwiftUI

TabView 有另一個 init  方法,它需要一個狀態變量,當中包含標籤的 tag value。

TabView(selection: $selectedIndex)

舉個例子,讓我們在 ContentView 內宣告以下狀態變數:

@State private var selectedIndex = 0

現在,如果我們改變 selectedIndex 的數值,標籤視圖就會自動轉換到相應的標籤。我們可以這樣更改程式碼來測試一下:

TabView(selection: $selectedIndex) {
   .
   .
   .
}
.onAppear {
    selectedIndex = 2
}

你會發現,在顯示標籤視圖時,會自動選擇第三個標籤。

建立一個客製化的可滾動標籤列

swiftui-animated-custom-tab-bar

如上圖的範例結果可見,我們想構建的標籤列是可滾動的,如果我們想放多於 5 個項目到標籤列,這個功能就十分有用了。要建立這個客製化標籤列,我們會用  ScrollViewScrollViewReader 來構建自己的視圖。

讓我們這樣構建標籤列視圖,並把它命名為 TabBarView

struct TabBarView: View {
    var tabbarItems: [String]

    @State var selectedIndex = 0

    var body: some View {
        ScrollViewReader { scrollView in
            ScrollView(.horizontal, showsIndicators: false) {
                HStack {
                    ForEach(tabbarItems.indices, id: \.self) { index in

                        Text(tabbarItems[index])
                            .font(.subheadline)
                            .padding(.horizontal)
                            .padding(.vertical, 4)
                            .foregroundColor(selectedIndex == index ? .white : .black)
                            .background(Capsule().foregroundColor(selectedIndex == index ? .purple : .clear))
                            .onTapGesture {
                                withAnimation(.easeInOut) {
                                    selectedIndex = index
                                }
                            }
                    }
                }
            }
            .padding()
            .background(Color(.systemGray6))
            .cornerRadius(25)

        }

    }
}

這個客製化標籤視圖可以接受一個陣列的標籤列項目。在這個範例中,我們會使用 String 陣列。但在實際 App 中,大家可以為選擇自己的客製化型別。

為了在標籤列中啟用滾動功能,我們會把所有標籤項目嵌入到一個滾動視圖中。此外,我們會用 ScrollViewReader 包裝滾動視圖,以確保所選的標籤項目是可見的。

在選擇特定標籤項目時,我們更新了 selectedIndex 變數來反映所選的 index。如此一來,我們就可以 highlight 所選的標籤項目,並向使用者提供反饋。

manually-scrollable-tab-bar

我們可以在預覽添加 TabBarView,來預覽這個客製化的標籤列。

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()

        TabBarView(tabbarItems: [ "Random", "Travel", "Wallpaper", "Food", "Interior Design" ]).previewDisplayName("TabBarView")
    }
}

現在,這個客製化的標籤列可以正常操作。但是,我們需要手動滾動標籤列,才能顯示最後一個項目。要解決這個問題,我們可以把以下程式碼添加到 ScrollView

.onChange(of: selectedIndex) { index in
    withAnimation {
        scrollView.scrollTo(index, anchor: .center)
    }
}

當所選 index 更新後,我們會呼叫 scrollTo 方法來移動滾動視圖。

利用 matchedGeometryEffect 建立更漂亮的動畫

我們建立了一個動態、而且可滾動的標籤列,但我們其實可以建立更漂亮的動畫。現在,在切換標籤項目時,標籤列使用「淡出」動畫。如果我們在標籤列搭配使用 matchedGeometryEffect,就可以創建更流暢更漂亮的動畫。讓我們看看如何實作吧!

首先,為標籤列項目建立一個新結構 TabbarItem

struct TabbarItem: View {
    var name: String
    var isActive: Bool = false
    let namespace: Namespace.ID

    var body: some View {
        if isActive {
            Text(name)
                .font(.subheadline)
                .padding(.horizontal)
                .padding(.vertical, 4)
                .foregroundColor(.white)
                .background(Capsule().foregroundColor(.purple))
                .matchedGeometryEffect(id: "highlightmenuitem", in: namespace)
        } else {
            Text(name)
                .font(.subheadline)
                .padding(.horizontal)
                .padding(.vertical, 4)
                .foregroundColor(.black)
        }

    }
}

有了 matchedGeometryEffect,我們只需要描述兩個視圖的外觀即可。然後,修飾符就會計算兩個視圖之間的差異,並自動為大小或位置變化設置動畫。因此,在上面的程式碼中,我們把被選擇的標籤項目 highlight 為紫色,而沒被選擇的項目則以普通文本樣式顯示。

TabBarView 中,宣告一個新的 namespace 變數:

@Namespace private var menuItemTransition

然後,這樣重新編寫 ForEach loop 的程式碼:

ForEach(tabbarItems.indices, id: \.self) { index in

    TabbarItem(name: tabbarItems[index], isActive: selectedIndex == index, namespace: menuItemTransition)
        .onTapGesture {
            withAnimation(.easeInOut) {
                selectedIndex = index
            }
        }
}

更新程式碼後,你會發現切換標籤項目的動畫變得更流暢和漂亮了。

swiftui-matchedgeometryeffect-tab-bar

使用客製化標籤列

在把 TabBarView 應用到 ContentView 之前,我們需要先在 TabBarView 做一個小改動。在 TabBarView 中,這樣把狀態變數修改為綁定變數:

@Binding var selectedIndex: Int

現在,我們就可以把這個客製化標籤列應用到其他視圖了。在 ContentView 這樣更新 body

ZStack(alignment: .bottom) {
    TabView(selection: $selectedIndex) {
        ForEach(colors.indices, id: \.self) { index in
            colors[index]
                .frame(maxWidth: .infinity, maxHeight: .infinity)
                .tag(index)
                .ignoresSafeArea()
        }
    }
    .ignoresSafeArea()

    TabBarView(tabbarItems: tabbarItems, selectedIndex: $selectedIndex)
        .padding(.horizontal)
}

要把客製化標籤列合併到 App 中十分簡單,我們只需要把 TabView 包裝在 ZStack 中,並在上面疊加 TabBarView,就可以輕鬆地把將標籤列合併到 tab UI。

我們還需要更新預覽結構,來讓專案順利執行:

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()

        TabBarView(tabbarItems: [ "Random", "Travel", "Wallpaper", "Food", "Interior Design" ], selectedIndex: .constant(0)).previewDisplayName("TabBarView")
    }
}

現在讓我們測試一下標籤列的 UI 吧:

animated-scrollable-tab-bar-swiftui

總結

對很多流行的手機 App 來說,標籤列介面是非常重要的元素,讓使用者可以快速和方便地切換到 App 的不同功能。雖然在大多數情況下,標準的標籤列都已經可以滿足我們的要求,但有時我們還是會希望客製化標籤列,以提升使用者體驗。

在這篇教學文章中,我們構建了一個動態、而且可滾動標籤列,並支援無限的標籤項目。我們還可以搭配 matchedGeometryEffect 使用,把標籤列的動畫提升到另一個層次。學會了這篇文章的技巧後,你就可以按自己 App 的需要,設計出無縫而且直觀的客製化標籤列。

如果大家想更深入了解 SwiftUI,可以參閱我們的《精通SwiftUI》一書。

譯者簡介:Kelly Chan-AppCoda 編輯小姐。
作者
Simon Ng
軟體工程師,AppCoda 創辦人。著有《iOS 17 App 程式設計實戰心法》、《iOS 17 App程式設計進階攻略》以及《精通SwiftUI》。曾任職於HSBC, FedEx等跨國企業,專責軟體開發、系統設計。2012年創立AppCoda技術部落格,定期發表iOS程式教學文章。現時專注發展AppCoda業務,致力於iOS程式教學、產品設計及開發。你可以到推特與我聯絡。
評論
更多來自 AppCoda 中文版
如何使用 Vision APIs 從圖像中辨識文字
AI

如何使用 Vision APIs 從圖像中辨識文字

Vision 框架長期以來一直包含文字識別功能。我們已經有詳細的教程,向你展示如何使用 Vision 框架掃描圖像並執行文字識別。之前,我們使用了 VNImageRequestHandler 和 VNRecognizeTextRequest 來從圖像中提取文字。 多年來,Vision 框架已經顯著演變。在 iOS 18 中,Vision
iOS 18更新:SwiftUI 新功能介紹
SwiftUI 框架

iOS 18更新:SwiftUI 新功能介紹

SwiftUI的技術不斷演進,每次更新都讓 iOS 應用程式開發變得更加便捷。隨著 iOS 18 Beta 的推出,SwiftUI 引入了多個令人興奮的新功能,使開發者僅需幾行程式碼即可實現出色的效果。 本教學文章旨在探索這個版本中的幾項主要改進,幫助你了解如何運用這些新功能。 浮動標籤列 (Floating Tab Bar)SwiftUI中的標籤視圖(Tab
很好! 你已成功註冊。
歡迎回來! 你已成功登入。
你已成功訂閱 AppCoda 中文版 電子報。
你的連結已失效。
成功! 請檢查你的電子郵件以獲取用於登入的連結。
好! 你的付費資料已更新。
你的付費方式並未更新。