开发语言:SwiftUI 2.0
开发环境:Xcode 12.0.1
发布平台:IOS 14
SwiftUI使用VStack/HStack/ZStack,来包含多个界面,并且设置它们在其之中的对齐方式,通常有3种使用方式。
1 默认方式
在使用VStack和HStack时,可以指定其对齐方式。下面的代码分别展示了VStack和HStack的对齐方式和效果。
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)
}
}
}
通常情况下,默认对齐已经可以满足我们的需求,也是我们在开发中使用最多的对齐方式。
2 使用alignmentGuide设置对齐
我们可以通过alignmentGuide,为Stack中的某一项指定不同的对齐方式,事实上,默认对齐也是调用了alignmentGuide来设置对齐的,首先我们看一下alignmentGuide的相关定义。
public func alignmentGuide(_ g: HorizontalAlignment,
computeValue: @escaping (ViewDimensions) -> CGFloat) -> some View
public func alignmentGuide(_ g: VerticalAlignment,
computeValue: @escaping (ViewDimensions) -> CGFloat) -> some View
第一个参数HorizontalAlignment和VerticalAlignment就是我们在默认对齐方式中使用的对齐类型,我们更关心的是第二个参数。
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记录的是当前View的高和宽
- 四个subscript为下标取值的方式,传递一个对齐方式,获取按照该对齐方式对齐的值,例如一个width为300的View,他的. trailing就是300。
为了解释清楚alignmentGuide的运作原理,我们按照以下方法实现一个自定义的对齐方式。
extension HorizontalAlignment {
private enum HAlignment: AlignmentID {
static func defaultValue(in dimensions: ViewDimensions) -> CGFloat {
return 800
}
}
static let myHAlignment = HorizontalAlignment(HAlignment.self)
}
我们实现了AlignmentID接口,其中包含一个defaultValue,我们使用的leading/center/trailing也是实现了这个接口,他们的默认值分别为0,ViewDimensions.width/2,ViewDimensions.width。这个值表示,从View的原点(左上角位置)偏移defaultValue(右为正,下为正)后,与Stack的基线对齐。
以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的值,设置到第一个参数指定的对齐类型中,替换掉它的defaultValue。
Stack在布局的时候,首先先确认设置的对齐类型,这里我们使用的是自定义类型myHAlignment,然后查找每个子控件的ViewDimensions中myHAlignment的值,此时这个值已经在alignmentGuide中设置过,然后与基线对齐,最终呈现整个Stack。
- 如果我们没有通过alignmentGuide设置Stack的对齐方式的值,布局时则会使用默认值。
通过图中的标出的VStack的基线,解释了5个不同的alignmentGuide设置对齐的方式。
通过上例可以看出Stack只关心和它对齐方式一致的值,但我们通过alignmentGuide设置值时,第一个参数不一定要和Stack中设置的对齐方式一致,如下例:
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)
//设置与VStack不一样的对齐方式
.alignmentGuide(.leading, computeValue: { dimension in
return 50
})
//此处拿到的.leading已经不是0,而是50
.alignmentGuide(.myHAlignment, computeValue: { dimension in
return dimension[.trailing] + dimension[explicit: .leading]!
})
}.background(Color.gray)
}
}
我们将.leading的值,设置为了50,然后在第二个alignmentGuide,我们通过dimension[explicit: .leading]拿到设置的值,与其他值组合后设置到.myHAlignment内,供Stack布局时使用。
这里也演示了dimension[explicit: .leading]的作用,它返回的是一个可选型,表示如果通过alignmentGuide设置过.leading的值,则可以获取,否则返回nil。
- 在使用alignmentGuide设置值后,不管通过dimension[explicit: ]或者dimension[],获取到的值时相同的。
3 自定义对齐方式
在上一小节中,我们使用了自定义的对齐方式,而自定义的对齐方式,往往可以帮助我们解决一些特殊的对齐需求,先看下面的例子:
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)
}
}
例子中,我们自定义了一个对齐方式,默认的对齐与.leading保持一致,然后我们将HStack中第二个Text的leading设置为myHAlignment的值,这样VStack的基线位置就是HStack中的第二个Text保持一致,VStack的其余部件布局时,会按照这个基线进行对齐。
如果我们尝试不使用myHAlignment,而直接使用.leading对齐方式。
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)
}
}
此时对Text("second")的alignmentGuide设置没有起到任何效果。
但这里设置.leading没有起作用的原因我也不太了解,猜测可能是.leading作为系统自带对齐方式,无法跨Stack传递或者者会在传递时设置初值。