SwiftUI2.0 使用Stack和alignmentGuide设置对齐

开发语言: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)
        }
    }
}
VStack的三种对齐方式
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)
        }
    }
}
HStack的三种对齐方式

通常情况下,默认对齐已经可以满足我们的需求,也是我们在开发中使用最多的对齐方式。

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传递或者者会在传递时设置初值。

你可能感兴趣的:(SwiftUI2.0 使用Stack和alignmentGuide设置对齐)