開發(fā)語言:SwiftUI 2.0
開發(fā)環(huán)境:Xcode 12.0.1
發(fā)布平臺(tái):IOS 14
SwiftUI使用VStack/HStack/ZStack,來包含多個(gè)界面,并且設(shè)置它們?cè)谄渲械膶?duì)齊方式,通常有3種使用方式。
1 默認(rèn)方式
在使用VStack和HStack時(shí),可以指定其對(duì)齊方式。下面的代碼分別展示了VStack和HStack的對(duì)齊方式和效果。
struct MainView: View {
var body: some View {
VStack{
VStack(alignment: .leading){
Text("first").background(Color.red)
Text("second").background(Color.blue)
Text("third").background(Color.yellow)
}.background(Color.gray)
Spacer().fixedSize()
VStack(alignment: .center){
Text("first").background(Color.red)
Text("second").background(Color.blue)
Text("third").background(Color.yellow)
}.background(Color.gray)
Spacer().fixedSize()
VStack(alignment: .trailing){
Text("first").background(Color.red)
Text("second").background(Color.blue)
Text("third").background(Color.yellow)
}.background(Color.gray)
}
}
}

struct MainView: View {
var body: some View {
VStack{
HStack(alignment: .top){
Text("first").background(Color.red).frame(width:20)
Text("second").background(Color.blue).frame(width:20)
Text("third").background(Color.yellow).frame(width:15)
}.background(Color.gray)
Spacer().fixedSize()
HStack(alignment: .center){
Text("first").background(Color.red).frame(width:20)
Text("second").background(Color.blue).frame(width:20)
Text("third").background(Color.yellow).frame(width:15)
}.background(Color.gray)
Spacer().fixedSize()
HStack(alignment: .bottom){
Text("first").background(Color.red).frame(width:20)
Text("second").background(Color.blue).frame(width:20)
Text("third").background(Color.yellow).frame(width:15)
}.background(Color.gray)
}
}
}

通常情況下,默認(rèn)對(duì)齊已經(jīng)可以滿足我們的需求,也是我們?cè)陂_發(fā)中使用最多的對(duì)齊方式。
2 使用alignmentGuide設(shè)置對(duì)齊
我們可以通過alignmentGuide,為Stack中的某一項(xiàng)指定不同的對(duì)齊方式,事實(shí)上,默認(rèn)對(duì)齊也是調(diào)用了alignmentGuide來設(shè)置對(duì)齊的,首先我們看一下alignmentGuide的相關(guān)定義。
public func alignmentGuide(_ g: HorizontalAlignment,
computeValue: @escaping (ViewDimensions) -> CGFloat) -> some View
public func alignmentGuide(_ g: VerticalAlignment,
computeValue: @escaping (ViewDimensions) -> CGFloat) -> some View
第一個(gè)參數(shù)HorizontalAlignment和VerticalAlignment就是我們?cè)谀J(rèn)對(duì)齊方式中使用的對(duì)齊類型,我們更關(guān)心的是第二個(gè)參數(shù)。
struct ViewDimensions {
var height: CGFloat { get }
var width: CGFloat { get }
subscript(guide: HorizontalAlignment) -> CGFloat { get }
subscript(guide: VerticalAlignment) -> CGFloat { get }
subscript(explicit guide: VerticalAlignment) -> CGFloat? { get }
subscript(explicit guide: HorizontalAlignment) -> CGFloat? { get }
}
- height和width記錄的是當(dāng)前View的高和寬
- 四個(gè)subscript為下標(biāo)取值的方式,傳遞一個(gè)對(duì)齊方式,獲取按照該對(duì)齊方式對(duì)齊的值,例如一個(gè)width為300的View,他的. trailing就是300。
為了解釋清楚alignmentGuide的運(yùn)作原理,我們按照以下方法實(shí)現(xiàn)一個(gè)自定義的對(duì)齊方式。
extension HorizontalAlignment {
private enum HAlignment: AlignmentID {
static func defaultValue(in dimensions: ViewDimensions) -> CGFloat {
return 800
}
}
static let myHAlignment = HorizontalAlignment(HAlignment.self)
}
我們實(shí)現(xiàn)了AlignmentID接口,其中包含一個(gè)defaultValue,我們使用的leading/center/trailing也是實(shí)現(xiàn)了這個(gè)接口,他們的默認(rèn)值分別為0,ViewDimensions.width/2,ViewDimensions.width。這個(gè)值表示,從View的原點(diǎn)(左上角位置)偏移defaultValue(右為正,下為正)后,與Stack的基線對(duì)齊。
以VStack為例,說明alignmentGuide的使用方法。
struct MainView: View {
var body: some View {
VStack(alignment: .myHAlignment){
Text("first")
.background(Color.red)
.alignmentGuide(.myHAlignment, computeValue: { dimension in
return dimension[.leading]
})
Text("second")
.background(Color.blue)
.alignmentGuide(.myHAlignment, computeValue: { dimension in
return dimension[.trailing]
})
Text("third")
.background(Color.yellow)
.alignmentGuide(.myHAlignment, computeValue: { dimension in
return dimension[HorizontalAlignment.center]
})
Text("fourth")
.background(Color.green)
.alignmentGuide(.myHAlignment, computeValue: { dimension in
return 40
})
Text("fifth")
.background(Color.secondary)
.alignmentGuide(.myHAlignment, computeValue: { dimension in
return -20
})
}.background(Color.gray)
}
}
alignmentGuide的作用是,將computeValue的值,設(shè)置到第一個(gè)參數(shù)指定的對(duì)齊類型中,替換掉它的defaultValue。
Stack在布局的時(shí)候,首先先確認(rèn)設(shè)置的對(duì)齊類型,這里我們使用的是自定義類型myHAlignment,然后查找每個(gè)子控件的ViewDimensions中myHAlignment的值,此時(shí)這個(gè)值已經(jīng)在alignmentGuide中設(shè)置過,然后與基線對(duì)齊,最終呈現(xiàn)整個(gè)Stack。
- 如果我們沒有通過alignmentGuide設(shè)置Stack的對(duì)齊方式的值,布局時(shí)則會(huì)使用默認(rèn)值。
通過圖中的標(biāo)出的VStack的基線,解釋了5個(gè)不同的alignmentGuide設(shè)置對(duì)齊的方式。

通過上例可以看出Stack只關(guān)心和它對(duì)齊方式一致的值,但我們通過alignmentGuide設(shè)置值時(shí),第一個(gè)參數(shù)不一定要和Stack中設(shè)置的對(duì)齊方式一致,如下例:
struct MainView: View {
var body: some View {
VStack(alignment: .myHAlignment){
Text("first")
.background(Color.red)
.alignmentGuide(.myHAlignment, computeValue: { dimension in
return dimension[.leading]
})
Text("second")
.background(Color.blue)
//設(shè)置與VStack不一樣的對(duì)齊方式
.alignmentGuide(.leading, computeValue: { dimension in
return 50
})
//此處拿到的.leading已經(jīng)不是0,而是50
.alignmentGuide(.myHAlignment, computeValue: { dimension in
return dimension[.trailing] + dimension[explicit: .leading]!
})
}.background(Color.gray)
}
}

我們將.leading的值,設(shè)置為了50,然后在第二個(gè)alignmentGuide,我們通過dimension[explicit: .leading]拿到設(shè)置的值,與其他值組合后設(shè)置到.myHAlignment內(nèi),供Stack布局時(shí)使用。
這里也演示了dimension[explicit: .leading]的作用,它返回的是一個(gè)可選型,表示如果通過alignmentGuide設(shè)置過.leading的值,則可以獲取,否則返回nil。
- 在使用alignmentGuide設(shè)置值后,不管通過dimension[explicit: ]或者dimension[],獲取到的值時(shí)相同的。
3 自定義對(duì)齊方式
在上一小節(jié)中,我們使用了自定義的對(duì)齊方式,而自定義的對(duì)齊方式,往往可以幫助我們解決一些特殊的對(duì)齊需求,先看下面的例子:
extension HorizontalAlignment {
private enum HAlignment: AlignmentID {
static func defaultValue(in dimensions: ViewDimensions) -> CGFloat {
return dimensions[HorizontalAlignment.leading]
}
}
static let myHAlignment = HorizontalAlignment(HAlignment.self)
}
struct MainView: View {
var body: some View {
VStack(alignment: .myHAlignment){
HStack {
Text("first")
.background(Color.red)
Text("second")
.background(Color.blue)
.alignmentGuide(HorizontalAlignment.myHAlignment, computeValue: { dimension in
return dimension[.leading]
})
Text("third")
.background(Color.yellow)
}
Text("fourth")
.background(Color.green)
}.background(Color.gray)
}
}

例子中,我們自定義了一個(gè)對(duì)齊方式,默認(rèn)的對(duì)齊與.leading保持一致,然后我們將HStack中第二個(gè)Text的leading設(shè)置為myHAlignment的值,這樣VStack的基線位置就是HStack中的第二個(gè)Text保持一致,VStack的其余部件布局時(shí),會(huì)按照這個(gè)基線進(jìn)行對(duì)齊。
如果我們嘗試不使用myHAlignment,而直接使用.leading對(duì)齊方式。
struct MainView: View {
var body: some View {
VStack(alignment: .leading){
HStack {
Text("first")
.background(Color.red)
Text("second")
.background(Color.blue)
.alignmentGuide(.leading, computeValue: { dimension in
return dimension[.leading]
})
Text("third")
.background(Color.yellow)
}
Text("fourth")
.background(Color.green)
}.background(Color.gray)
}
}
此時(shí)對(duì)Text("second")的alignmentGuide設(shè)置沒有起到任何效果。

但這里設(shè)置.leading沒有起作用的原因我也不太了解,猜測(cè)可能是.leading作為系統(tǒng)自帶對(duì)齊方式,無法跨Stack傳遞或者者會(huì)在傳遞時(shí)設(shè)置初值。