本系列倒數第三篇了,接下來要讓 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 可以直接套,但是沒有也沒關係,反而可以對畫面元件的關係性學得更徹底,希望大家也有所收穫。
參考網站