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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350