Alignment guides是用一种功能强壮但一般未被充分利用的布局工具。在许多情况下,它们能够帮助咱们防止更复杂的选项,例如Anchor preferences。就如下图所示,改动alignment也能够主动并轻松的实现动画作用。

SwiftUI中的Alignment Guides

假如你发现Alignment guides的确有效,可是又没有达到预期的作用,很或许是没有意识到有一些隐式的Alignment guides在起作用。请记住:容器中的每一个视图都有一个Alignment guide。

什么是Alignment Guide

Alignment guide本质上是一个浮点型数值,它在视图中设置一个点,用来标识视图间对齐的根据。对齐能够是水平方法也能够是笔直方法,咱们先来看下水平的对齐方法。

假定咱们有3个视图(A,B和C),它们的水平基线分别为0,20和10。咱们将对视图进行定位,使View A的起点(leading,水平偏移0)与View B的开端第20个点(leading,水平偏移20)和view C的开端第10个点(leading,水平偏移10)对齐:

SwiftUI中的Alignment Guides

相同的概念也适用于笔直对齐:

SwiftUI中的Alignment Guides

从这些比如能够看出,笔直容器(VStack)需求水平对齐,水平容器(HStack)则需求笔直对齐,或许觉得很古怪,但稍微想一下的确如此,VStack里面的视图是笔直摆放,充分占有容器的高,只能在水平方向移动来对齐,而HStack则正好相反。

别的,ZStack同时需求水平对齐和笔直对齐。

开端吧

先来一张图让你感受一下:

SwiftUI中的Alignment Guides

你或许有点慌,可是这没什么,这里是一些基本的描绘,咱们会在后面进一步讨论。

  • Container Alignment: 容器对齐,它有两个作用,它决议了哪些alignmentGuides()会被疏忽,同时它也为其包含的没有指定Alignment guide的视图界说了默许的Alignment guide;
  • Alignment Guide: 假如该值和Container Alignment的对齐参数不匹配,布局时将会被疏忽;
  • Implicit Alignment Value: 隐式对齐值,浮点型数值,有一些便利的预设值能够运用,如d.widthd[.leading]d[.center]等等。这是一个与指定Guide相关的默许值;
  • Explicit Alignment Value: 显现对齐值,浮点型数值,表明修正视图Guide的方位。需求经过编码核算得到;
  • Frame Alignment: 框架对齐,表明容器内一切的视图(作为一个全体)的对齐方法;
  • Text Alignment: 多行文本在视图中的对齐方法。

Implicit vs. Explicit Alignments

容器中每个视图都有一个对齐方法。 为什么总是杰出显现这句话,由于这是需求记住的最重要的概念之一。当咱们经过.alignmentGuide()来界说对齐方法时,对齐方法是显现的(Explicit),当咱们没有指定,对齐方法便是隐式的(Implicit)。隐式对齐方法的值将由容器视图中的对齐方法(Container Alignment)参数供给,如VStack(alignment: .leading)

你或许会问,假如咱们不为VStackHStackZStack指定对齐方法参数会怎么样?很简单,它们都有一个默许值:.center

ViewDimensions

到目前为止,咱们看到在.alignmentGuide()修饰符的闭包中,咱们需求返回一个CGFloat的值作为对齐攻略的值,来看下.alignmentGuide()方法的界说:

func alignmentGuide(_ g: HorizontalAlignment, computeValue: @escaping (ViewDimensions) -> CGFloat) -> some View
func alignmentGuide(_ g: VerticalAlignment, computeValue: @escaping (ViewDimensions) -> CGFloat) -> some View

这是一个重载方法,有两个版别,一个用于水平对齐,一个用于笔直对齐。computeValue闭包供给了一个ViewDimensions类型的参数,该类型是一个struct,包含了一些咱们能够用来创立对齐攻略的关于视图的信息:

public struct ViewDimensions {
    public var width: CGFloat { get } // The view's width
    public var height: CGFloat { get } // The view's height
    public subscript(guide: HorizontalAlignment) -> CGFloat { get }
    public subscript(guide: VerticalAlignment) -> CGFloat { get }
    public subscript(explicit guide: HorizontalAlignment) -> CGFloat? { get }
    public subscript(explicit guide: VerticalAlignment) -> CGFloat? { get }
}

高度和宽度没什么好说的,便是咱们当前处理的视图的高度和宽度。可是有一些subscript方法就有点不好理解,咱们先看看怎么拜访它们:

Text("Hello")
    .alignmentGuide(HorizontalAlignment.leading, computeValue: { d in                        
        return d[HorizontalAlignment.leading] + d.width / 3.0 - d[explicit: VerticalAlignment.top]
    })

稍后咱们再探究HorizontalAlignmentVerticalAlignmentAlignment类型,看看这些是什么。

Alignment的不清晰运用

一般咱们不需求像这样指定对齐攻略的全名:

d[HorizontalAlignment.leading] + d.width / 3.0 - d[explicit: VerticalAlignment.top]

编译器能够推断出咱们指的是HorizontalAlignment仍是VerticalAlignment,因而咱们只需求运用:

d[.leading] + d.width / 3.0 - d[explicit: .top]

不过,在有些情况下,编译器或许会提示对齐方法不清晰,尤其是在运用.center的时候,由于.center有两种类型:HorizontalAlignment.centerVerticalAlignment.center,这时候就需求指定全名。

HorizontalAlignment类型

运用HorizontalAlignment类型拜访ViewDimensions时,咱们能够获得视图的leading、视图的center和视图的trailing:

extension HorizontalAlignment {
    public static let leading: HorizontalAlignment
    public static let center: HorizontalAlignment
    public static let trailing: HorizontalAlignment
}

注意,引用能够经过两种方法获取:

d[.trailing]
d[explicit: .trailing]

第一个是隐式值,也便是对齐攻略的默许值,一般情况下,.leading的默许值是0.center的默许值是width/2.trailing的默许值是width。不过,有时候也能够获取显现值,例如在ZStack中,当核算.leading时,或许会用到.top的显现值。

VerticalAlignment类型

VerticalAlignmentHorizontalAlignment类似,但它多了两个属性firstTextBaseline(最上面的文本基线)和lastTextBaseline(最下面的文本基线),在对齐一行不同字体大小的文本时很有用。

extension VerticalAlignment {
    public static let top: VerticalAlignment
    public static let center: VerticalAlignment
    public static let bottom: VerticalAlignment
    public static let firstTextBaseline: VerticalAlignment
    public static let lastTextBaseline: VerticalAlignment
}

Alignment类型

之前说过,ZStack需求指定两种对齐方法(一种水平对齐,一种笔直对齐),所以有了Alignment类型,它将这两种对齐方法结合在一起。例如,假如咱们想要一个顶部笔直对齐方法和前缘水平对齐方法(便是左上角),咱们有两种做法:

ZStack(alignment: Alignment(horizontal: .leading, vertical: .top)) { ... }

或者最常见的:

ZStack(alignment: .topLeading) { ... }

稍后当咱们开端运用自界说的Alignment guides时,第一种方法就会很有用。

Container Alignment

容器视图(VStack、HStack和ZStack)中的对齐参数有两种作用:

  1. 确认哪些.alignmentGuides()与布局有关,一切与容器参数中的对齐方法不同的alignment guides都将被疏忽;
  2. 为没有指定显现对齐方法的视图供给隐式对齐方法,即子视图的默许值。

在下面的动画中,你能够看到改动容器中的对齐方法将如何决议哪些alignment guides在布局时收效。

Frame Alignment

到目前为止,咱们看到的一切对齐方法都是关于如何将一个视图相关于别的一个视图定位。一旦确认了这一点,布局系统就需求在容器内定位整个视图组,能够经过frame(alignment:)来修正整个视图组在容器中的对齐方法,假如不指定,视图组将在容器内居中。

SwiftUI中的Alignment Guides

一般情况下,修正frame的alignment不会发生任何影响,这是正常的,由于容器默许是紧凑的,容器的大小刚好容纳一切的视图,因而在frame()修饰符中运用.leading.center.trailing都不会有任何作用,视图组已经占用了一切的空间,无法再移动,除非指定frame的大小,让它有移动的空间。

多文本Alignment

这东西比较简单,指定多行文本的对齐方法。

SwiftUI中的Alignment Guides

自界说Alignment

已然咱们已经知道了规范对齐的工作方法,那咱们就来创立一个自界说的对齐方法。先看看第一个示例:

extension HorizontalAlignment {
    private enum WeirdAlignment: AlignmentID {
        static func defaultValue(in d: ViewDimensions) -> CGFloat {
            return d.height
        }
    }
    static let weirdAlignment = HorizontalAlignment(WeirdAlignment.self)
}

自界说Alignment时,咱们需求做两件事:

  1. 确认是水平对齐仍是笔直对齐;
  2. 为隐式对齐方法(即没有清晰调用.alignmentGuide()的视图)供给默许值。

咱们将运用height作为默许值。这将会发生一种风趣的作用:

SwiftUI中的Alignment Guides

struct CustomView: View {
    var body: some View {
        VStack(alignment: .weirdAlignment, spacing: 10) {
            Rectangle()
                .fill(Color.primary)
                .frame(width: 1)
                .alignmentGuide(.weirdAlignment, computeValue: { d in d[.leading] })
            ColorLabel(label: "Monday", color: .red, height: 50)
            ColorLabel(label: "Tuesday", color: .orange, height: 70)
            ColorLabel(label: "Wednesday", color: .yellow, height: 90)
            ColorLabel(label: "Thursday", color: .green, height: 40)
            ColorLabel(label: "Friday", color: .blue, height: 70)
            ColorLabel(label: "Saturday", color: .purple, height: 40)
            ColorLabel(label: "Sunday", color: .pink, height: 40)
            Rectangle()
                .fill(Color.primary)
                .frame(width: 1)
                .alignmentGuide(.weirdAlignment, computeValue: { d in d[.leading] })
        }
    }
}
struct ColorLabel: View {
    let label: String
    let color: Color
    let height: CGFloat
    var body: some View {
        Text(label).font(.title).foregroundColor(.primary).frame(height: height).padding(.horizontal, 20)
            .background(RoundedRectangle(cornerRadius: 8).fill(color))
    }
}

对齐不同层次结构中的视图

在前面的示例中,咱们知道了如何创立自界说的对齐方法,但这有什么意义呢?不运用自界说对齐方法也能达到相同的作用。运用自界说对齐方法的真实作用在于,它能够对齐不同层次结构中的视图。

请看下一个示例:

SwiftUI中的Alignment Guides

假如咱们分析一下该视图的组件,就会发现咱们需求将图画与文本视图对齐,可是它们并不属于同一个容器:

SwiftUI中的Alignment Guides

图画和文本视图都有一个一起的容器(HStack),因而咱们需求创立一个自界说对齐方法,以匹配它们的中心点。重要的是要记住适当设置一起容器的对齐参数。

extension VerticalAlignment {
    private enum MyAlignment : AlignmentID {
        static func defaultValue(in d: ViewDimensions) -> CGFloat {
            return d[.bottom]
        }
    }
    static let myAlignment = VerticalAlignment(MyAlignment.self)
}
struct CustomView: View {
    @State private var selectedIdx = 1
    let days = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
    var body: some View {
            HStack(alignment: .myAlignment) {
                Image(systemName: "arrow.right.circle.fill")
                    .alignmentGuide(.myAlignment, computeValue: { d in d[VerticalAlignment.center] })
                    .foregroundColor(.green)
                VStack(alignment: .leading) {
                    ForEach(days.indices, id: \.self) { idx in
                        Group {
                            if idx == self.selectedIdx {
                                Text(self.days[idx])
                                    .transition(AnyTransition.identity)
                                    .alignmentGuide(.myAlignment, computeValue: { d in d[VerticalAlignment.center] })
                            } else {
                                Text(self.days[idx])
                                    .transition(AnyTransition.identity)
                                    .onTapGesture {
                                        withAnimation {
                                            self.selectedIdx = idx
                                        }
                                }
                            }
                        }
                    }
                }
            }
            .padding(20)
            .font(.largeTitle)
    }
}

你或许会问,一切没有指定显现的笔直对齐方法的文本视图怎么办,莫非它们不会运用隐式值(默许值)吗?假如是这样,它们不都是堆叠在一起了?

这些问题都有道理。这是Alignment guides中另一个或许令人费解的问题。不过,在这种情形下,咱们处理的是VStack,而不是ZStack,这意味着它内部的一切视图都必须笔直摆放,Alignment guides不会破坏这一点。布局系统将运用所选视图中的显现对齐方法来对齐“→”图画,其他没有显现对齐方法的文本视图将相关于有显现对齐方法的视图进行定位。

自界说ZStack的Alignment

假如你需求为ZStack创立自界说对齐方法,这里有一个模板:

extension VerticalAlignment {
    private enum MyVerticalAlignment : AlignmentID {
        static func defaultValue(in d: ViewDimensions) -> CGFloat {
            return d[.bottom]
        }
    }
    static let myVerticalAlignment = VerticalAlignment(MyVerticalAlignment.self)
}
extension HorizontalAlignment {
    private enum MyHorizontalAlignment : AlignmentID {
        static func defaultValue(in d: ViewDimensions) -> CGFloat {
            return d[.leading]
        }
    }
    static let myHorizontalAlignment = HorizontalAlignment(MyHorizontalAlignment.self)
}
extension Alignment {
    static let myAlignment = Alignment(horizontal: .myHorizontalAlignment, vertical: .myVerticalAlignment)
}
struct CustomView: View {
    var body: some View {
        ZStack(alignment: .myAlignment) {
            ...
        }
    }
}

总结

在这篇文章中,咱们看到了Alignment guides的强壮功能,一旦你能掌握它,将会变得更有意义,为此需求牢记以下几点:

  1. 容器中每个视图都有一个Alignment guide,假如没有显现指定,将运用容器的对齐参数;
  2. 在布局过程中,与容器对齐参数指定的类型不同的Alignment guide将被疏忽;
  3. VStack运用HorizontalAlignment,而HStack运用VerticalAlignment
  4. 假如容器很紧凑,.frame(alignment:)中的对齐参数或许不会发生视觉作用;
  5. 对齐不同层次结构中的视图时,就需求用到自界说对齐。

原文出处:swiftui-lab.com/alignment-g…