Xcode — SwiftUI 做到類同 UISegmentedControl 效果
目錄
⦿ 前情提要
⦿ SwiftUI
⦿ 顏色
⦿ 位置
前情提要
在前一篇文章中,我們知道 SegmentedControl 並不是那麼容易客製化成我們要的效果,所以建構一個客製化的 View,來去處理切換 Segment 時顯示的切換,由於前篇使用了 UIKit,在這邊,我們也可以看看 SwiftUI 的效果如何。
這個頁籤的切換是放在 NavigationBar 上,做為推薦文章、熱門文章的切換,且左右切換像是滑動 ScrollView 來做呈現。
下面,我們來看看 SwiftUI 如何達成的。
繼續閱讀|回目錄
SwiftUI
從下圖,我們可以理解到如果這個 NavigationBar 的 View 是這樣排列,那麼最左邊、最右邊各是一個 Button,中間為兩個 Text,四者之間塞了三個 Spacer()
,這個 Spacer() 就如同 UIKit 的 fixedSpace
。
let fixedSpace = UIBarButtonItem(barButtonSystemItem: .fixedSpace,
target: nil,
action: nil)
我們知道 Button
、Text
、Text
、Button
結構會是用 HStack 包起來的。
struct HomeNavigationBar: View {
var body: some View {
HStack(alignment: .top, spacing: 0) {
Button()
Spacer()
Text("推薦")
Spacer()
Text("推薦")
Spacer()
Button()
}
}
}
但是 Text 下方有一個切換條
在跳動,所以應該是要把 Text
、Spacer()
、Text
用 HStack
先包起來,再把 HStack
、切換條
用 VStack 包起來,並且適時地更新切換條
的位置。
除此之外,選中的 Text 顏色比較深,未選中的 Text 顏色比較淺,於是,看起來有兩個變數了。
由於 Text 並不是可以互動的按鈕,所以幫他加上 onTapGesture,這個 onTapGesture 最好可以去更改前面說的兩個變數,即切換條的位置(在 UIKit 中為 Constraint
)會更新。
顏色
Text("推薦").opacity(Double(1 - offsetRatio * 0.5))
.onTapGesture {
withAnimation {
self.offsetRatio = 0
}
}
Text("熱門").opacity(Double(0.5 + offsetRatio * 0.5))
.onTapGesture {
withAnimation {
self.offsetRatio = 1
}
}
我們要做到的是將這個 offsetRatio 當作 Segment 的 Index,0
的時候為第一頁,1
的時候為第二頁。
也就是說,當我們加入 opacity
的時候,0
的時候第一頁為深色(opacity 為 1),第二頁為淺色(opacity 為 0.5);1
的時候第一頁為淺色(0.5),第二頁為深色(1)。
顏色確立了,下面來確認位置。
位置
在 VStack 中,包了一個圓角矩形,圓角半徑為 2,寬度為 Text 的一半,高度為 4,而重要的是它的 offset。
RoundedRectangle(cornerRadius: 2)
.foregroundColor(.blue)
.frame(width: 30, height: 4)
.offset(
x: screenWidth * 0.5 * (offsetRatio - 0.5) + kLabelWidth * (0.5 - offsetRatio)
)
// .frame(height: 6)
由於點擊 Text 為 0
與 1
的切換,這個 offset 也會依照 0 與 1 去更新。
不過我們先看看 VStack 的寬度。
VStack(spacing: 3) {
HStack {
Text("推薦")
Spacer()
Text("熱門")
}
RoundedRectangle(cornerRadius: 2)
}.frame(width: screenWidth * 0.5)
這個 VStack 如下,只占螢幕的一半。
這裡的計算要將橘色部份切成一半來看為 x/2
,Text 的寬度為 y
,圓角矩形的寬度為 y/2
,但這邊必須要知道當 offset = 0 時,這個圓角矩形
會在正中間,所以當 Index = 0 時,它要向左偏移
;Index = 1 時,它要向右偏移
。
所以當 Index = 0 時,它必須從中間向左(負的)一半 VStack Width,但必須加回圓角矩形的 Width(因為是從左邊端點移動
),即是 -x/2 + y/2
。
當 Index = 1 時,它必須從中間向右(正的)一半 VStack Width,但必須減掉圓角矩形的 Width(因為是從右邊端點移動
),即是 x/2-y/2
。
所以提取 (offsetRatio - 0.5)(x - y)
就是偏移的公式了!得到如下:
VStack(spacing: 3) {
HStack {
Text("推薦")
Spacer()
Text("熱門")
}
RoundedRectangle(cornerRadius: 2)
.offset(
x: (offsetRatio - 0.5)(screenWidth * 0.5 - kLabelWidth)
)
}.frame(width: screenWidth * 0.5)
最後,就可以在 HomeView 中設定 NavigationBar,如下:
struct HomeView: View {
@State var offsetRatio: CGFloat = 0
var body: some View {
let screenWidth = UIScreen.main.bounds.width
let screenHeight = UIScreen.main.bounds.height
NavigationView {
HScrollViewController(pageWidth: screenWidth,
contentSize: CGSize(
width: screenWidth * 2,
height: screenHeight
),
offsetRatio: self.$offsetRatio) {
}
.navigationBarItems(leading: HomeNavigationBar(offsetRatio: $offsetRatio))
}
}
}
在這個 NavigationView 中,有一個客製化的 HScrollViewController 能夠呈現水平滑動的 View,這個 View width 為螢幕寬的兩倍,表示有兩頁的 View 可以呈現,即是推薦
與熱門
頁的切換,然而他有一個 offsetRatio 的 @State 變數
,並且它的 navigationBarItems 放入一個客製化的 HomeNavigationBar,同樣地,offsetRatio 是它的變數。
這表示 offsetRatio 的變化,會改動 scrollView 的呈現,反之亦然,這即是說如果 scrollView 滾動到第一頁,Index 為 0,滾動到第二頁,Index 為 1;或者是剛才的 Button 改動 Index 為 0 或 1,scrollView 同樣在第一頁與第二頁間切換。
是不是很有邏輯呢?
這次就分享到這,感謝您的閱讀。
繼續閱讀|回目錄