iT邦幫忙

2023 iThome 鐵人賽

DAY 28
0
Mobile Development

用 SwiftUI 魔法變出 Leetcode 刷題知識學習 App!系列 第 28

Day 28: SwiftUI 展示 LeetCode 頁籤滑動換頁: TabView 實作

  • 分享至 

  • xImage
  •  

本系列倒數第三篇了,接下來要讓 LeetCode 內頁更豐富,可以注意到每次打開 LeetCode 看題目詳細的時候,會發現上面有選擇 Tab 可以讓我們切換不同頁面,第一個 Tab 豪無疑問是題目描述,第二個是評論,第三個是題解,第四個是提交紀錄

要如何做到四個 Tab 可在 App 上自由切換呢?就是今天的 SwiftUI 要實現的功能 TabView

首先基本 TabView 使用最常見按鈕會在下面,利用每一頁的主體元件,這裡是 Text去調整 Modifier,新增 .tabItem 以及 .tag(索引位置),去設定與 TabView 的畫面關係。

struct ContentView: View {
    
    var body: some View {
        TabView {
            // 第一個選項卡
            Text("首頁")i
                .tabItem {
                    Image(systemName: "house.fill")
                    Text("首頁")
                }
                .tag(0) // 用來識別這個選項卡
            
            // 第二個選項卡
            Text("設置")
                .tabItem {
                    Image(systemName: "gear")
                    Text("設置")
                }
                .tag(1)
        }
        
    }
    
}

App 畫面如下,這就是常見 App 主體架構長的樣子。

而我們想要把 Tab 放到畫面上方,才是 LeetCode 題目詳細頁的效果。

於是先建立了一組 Button 在上方,讓按鈕可以按了之後切換頁面。

這是我們要新增的 LeetCode 題目內頁 Tab 資料。

let segments: [String] = ["題目描述", "評論", "題解", "提交說明"]
@State private var selected: String = "題目描述"

畫面程式碼如下:

HStack(spacing: 0) {
      ForEach(segments, id: \.self) { segment in
            Button {
                        selected = segment
            } label: {
                  VStack {
                            Text(segment)
                                .font(.headline)
                                .fontWeight(.medium)
                                .foregroundColor(selected == segment ? .black : Color(uiColor: .systemGray))
                            ZStack {
                                Capsule()
                                    .fill(Color.clear)
                                    .frame(height: 4)
                                if selected == segment {
                                    Capsule()
                                        .fill(Color.black)
                                        .frame(height: 2)
                                        .matchedGeometryEffect(id: "Tab", in: name)
                                }
                            }
                   }
             }
      }
                
 }.frame(alignment: .top)

這裡很特別的是 Capsule 這個元件那是膠囊形狀的 View,也就是為我們呈現每個按鈕底線效果用。

而兩個按鈕之間的底線動畫由 .matchedGeometryEffect 補足,它會因為前後差異去補中間動畫。

再來我們要補上按了按鈕會換頁,而且在頁面左右滑動也要更新按鈕,這個關係性需要用狀態補足。

先建置滑動頁面,程式碼如下:

TabView(selection: $currentPage) {
            // 第一個頁面
            Text("頁面 1")
                .tag(0)
                .onAppear {
                    currentPage = 0
                }

            // 第二個頁面
            Text("頁面 2")
                .tag(1)
                .onAppear {
                    currentPage = 1
                }

            // 第三個頁面
            Text("頁面 3")
                .tag(2)
                .onAppear {
                    currentPage = 2
                }
        }
        .tabViewStyle(PageTabViewStyle())
        // animate on scroll
        .animation(.easeIn)

如截圖,完成了左右滑成功切換頁面。

結合上面的按鈕跟下面的頁面互動,組合起來完整程式碼。

import SwiftUI

struct SegmentedView: View {

    let segments: [String] = ["題目描述", "評論", "題解", "提交說明"]
    @State private var selected: String = "題目描述"
    @Namespace var name
    @State private var currentPage = 0

    var body: some View {
        VStack{
            HStack(spacing: 0) {
                ForEach(segments.indices, id: \.self) { index in
                    Button {
                        selected = segments[index]
                        currentPage = index
                    } label: {
                        VStack {
                            Text(segments[index])
                                .font(.headline)
                                .fontWeight(.medium)
                                .foregroundColor(currentPage == index ? .black : Color(uiColor: .systemGray))
                            ZStack {
                                Capsule()
                                    .fill(Color.clear)
                                    .frame(height: 4)
                                if currentPage == index {
                                    Capsule()
                                        .fill(Color.black)
                                        .frame(height: 2)
                                        .matchedGeometryEffect(id: "Tab", in: name)
                                }
                            }
                        }
                    }
                }
                
            }.frame(alignment: .top)
            
            TabView(selection: $currentPage) {
                ForEach(segments.indices, id: \.self) { index in
                    Text("頁面 \(segments[index])")
                        .tag(index)
                    
                }
                
            }
            .tabViewStyle(PageTabViewStyle())
            .animation(.easeIn, value: UUID())
            
        }
        
    }
}

一個擁有 LeetCode 詳細頁上面四個頁籤的畫面效果就完成了!

我們把頁面 Text 取代成我們上次做的 LeetCode 題目詳細頁面,那就更完整了。

總結

經過這個 LeetCode 題目詳細頁面的製作,更能掌握一個複雜完整的 SwiftUI 畫面會是怎樣的呈現,有點意外這種常見的 UI 模式居然要自己組起來,本來以為會有現成的 View 可以直接套,但是沒有也沒關係,反而可以對畫面元件的關係性學得更徹底,希望大家也有所收穫。

參考網站


上一篇
Day 27: 導讀 LeetCode 演算法 - 動態規劃 Dynamic Programming (Swift)
下一篇
Day 29: SwiftUI Search bar 搜尋 LeetCode 列表,Section 顯示演算法主題
系列文
用 SwiftUI 魔法變出 Leetcode 刷題知識學習 App!30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言