SwiftUI 多个标签垂直对齐

Ric*_*oon 11 swiftui

有很多解决方案可以尝试使用 VStack 内的 HStack 在 SwiftUI 中对齐多个图像和文本。有什么办法可以实现多个标签吗?添加到列表中时,多个标签会自动垂直对齐。当它们嵌入到 VStack 中时,是否有一种简单的方法可以做到这一点?

在此输入图像描述

struct ContentView: View {
    var body: some View {
//        List{
        VStack(alignment: .leading){
            Label("People", systemImage: "person.3")
            Label("Star", systemImage: "star")
            Label("This is a plane", systemImage: "airplane")
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

rob*_*off 17

所以,你想要这个:

\n

三个 SwiftUI 标签的垂直堆栈。 顶部标签显示 \xe2\x80\x9cPeople\xe2\x80\x9d 并具有人物图标。 中间标签显示 \xe2\x80\x9cStar\xe2\x80\x9d 并具有星形图标。 底部标签显示 \xe2\x80\x9cThis is aplane\xe2\x80\x9d\xc2\xa0 并且具有平面图标。 图标的宽度不同,但它们的中心对齐。 标签标题的前缘也对齐。

\n

我们将实现一个名为 的容器视图,EqualIconWidthDomain以便我们可以使用以下代码绘制上面显示的图像:

\n
struct ContentView: View {\n    var body: some View {\n        EqualIconWidthDomain {\n            VStack(alignment: .leading) {\n                Label("People", systemImage: "person.3")\n                Label("Star", systemImage: "star")\n                Label("This is a plane", systemImage: "airplane")\n            }\n        }\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

您可以在这个要点中找到所有代码。

\n

为了解决这个问题,我们需要测量每个图标的宽度,并frame使用宽度的最大值将 a 应用于每个图标。

\n

SwiftUI 提供了一个名为 \xe2\x80\x9cpreferences\xe2\x80\x9d 的系统,视图可以通过该系统将值传递给其祖先,而祖先可以聚合这些值。为了使用它,我们创建一个符合以下条件的类型PreferenceKey,如下所示:

\n
fileprivate struct IconWidthKey: PreferenceKey {\n    static var defaultValue: CGFloat? { nil }\n\n    static func reduce(value: inout CGFloat?, nextValue: () -> CGFloat?) {\n        switch (value, nextValue()) {\n        case (nil, let next): value = next\n        case (_, nil): break\n        case (.some(let current), .some(let next)): value = max(current, next)\n        }\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

为了将最大宽度传递回标签,我们将使用 \xe2\x80\x9cenvironment\xe2\x80\x9d 系统。为此,我们需要一个EnvironmentKey. 在这种情况下,我们可以IconWidthKey再次使用。我们还需要添加一个EnvironmentValues使用键类型的计算属性:

\n
extension IconWidthKey: EnvironmentKey { }\n\nextension EnvironmentValues {\n    fileprivate var iconWidth: CGFloat? {\n        get { self[IconWidthKey.self] }\n        set { self[IconWidthKey.self] = newValue }\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

现在我们需要一种方法来测量图标的宽度,将其存储在首选项中,并将环境的宽度应用于图标。我们将创建一个ViewModifier来执行这些步骤:

\n
fileprivate struct IconWidthModifier: ViewModifier {\n    @Environment(\\.iconWidth) var width\n\n    func body(content: Content) -> some View {\n        content\n            .background(GeometryReader { proxy in\n                Color.clear\n                    .preference(key: IconWidthKey.self, value: proxy.size.width)\n            })\n            .frame(width: width)\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

要将修饰符应用于每个标签的图标,我们需要一个LabelStyle

\n
struct EqualIconWidthLabelStyle: LabelStyle {\n    func makeBody(configuration: Configuration) -> some View {\n        HStack {\n            configuration.icon.modifier(IconWidthModifier())\n            configuration.title\n        }\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

最后我们就可以编写EqualIconWidthDomain容器了。它需要从 SwiftUI 接收偏好值并将其放入其后代的环境中。它还需要将 应用于EqualIconWidthLabelStyle其后代。

\n
struct EqualIconWidthDomain<Content: View>: View {\n    let content: Content\n    @State var iconWidth: CGFloat? = nil\n\n    init(@ViewBuilder _ content: () -> Content) {\n        self.content = content()\n    }\n\n    var body: some View {\n        content\n            .environment(\\.iconWidth, iconWidth)\n            .onPreferenceChange(IconWidthKey.self) { self.iconWidth = $0 }\n            .labelStyle(EqualIconWidthLabelStyle())\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

请注意,EqualIconWidthDomain不必是 a VStackof Labels,并且图标不必是 SF Symbols 图像。例如,我们可以展示这个:

\n

两行两列的标签网格

\n

请注意,标签 \xe2\x80\x9cicons\xe2\x80\x9d 之一是Text. 所有四个图标都以相同的宽度布局(跨两列)。这是代码:

\n
struct FancyView: View {\n    var body: some View {\n        EqualIconWidthDomain {\n            VStack {\n                Text("Le Menu")\n                    .font(.caption)\n                Divider()\n                HStack {\n                    VStack(alignment: .leading) {\n                        Label(\n                            title: { Text("Strawberry") },\n                            icon: { Text("") })\n                        Label("Money", systemImage: "banknote")\n                    }\n                    VStack(alignment: .leading) {\n                        Label("People", systemImage: "person.3")\n                        Label("Star", systemImage: "star")\n                    }\n                }\n            }\n        }\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n