简体   繁体   English

如何在 AppKit 上的自定义 SwiftUI 表单中右对齐项目标签?

[英]How to right-align item labels in a custom SwiftUI form on AppKit?

I have the following Cocoa form:我有以下 Cocoa 表格:

struct Canvas: PreviewProvider {
  static var previews: some View {
    VStack {
      HStack(alignment: .firstTextBaseline) {
        Text("Endpoint:")
        TextField("https://localhost:8080/api", text: .constant(""))
      }
      Divider()
      HStack(alignment: .firstTextBaseline) {
        Text("Path:")
        TextField("/todos", text: .constant(""))
      }
      Spacer()
    }
    .padding()
    .previewLayout(.fixed(width: 280, height: 200))
  }
}

This panel looks nice but I'd like to right-align “Endpoint:” and “Path:” labels:这个面板看起来不错,但我想右对齐“端点:”和“路径:”标签:

So I apply a custom horizontal alignment:所以我应用了一个自定义的水平 alignment:

struct Canvas: PreviewProvider {
  static var previews: some View {
    VStack(alignment: .label) {
      HStack(alignment: .firstTextBaseline) {
        Text("Endpoint:").alignmentGuide(.label) { $0[.trailing] }
        TextField("https://localhost:8080/api", text: .constant(""))
      }
      Divider()
      HStack(alignment: .firstTextBaseline) {
        Text("Path:").alignmentGuide(.label) { $0[.trailing] }
        TextField("/todos", text: .constant(""))
      }
      Spacer()
    }
    .padding()
    .previewLayout(.fixed(width: 280, height: 200))
  }
}

extension HorizontalAlignment {
  private enum Label: AlignmentID {
    static func defaultValue(in context: ViewDimensions) -> CGFloat {
      context[.leading]
    }
  }
  static let label: HorizontalAlignment = .init(Label.self)
}

Results are not what I need however:结果不是我需要的:

There is no documentation, please help.没有文档,请帮忙。

I don't believe alignment guides will work here in their current implementation.我不相信 alignment 指南将在他们当前的实施中工作。 After playing with them a bit, it seems that they size their children based on the container's given size and then align each child based on the guide.在和他们玩了一会儿之后,他们似乎根据容器的给定大小调整了孩子的大小,然后根据指南对齐每个孩子。 This leads to the weird behavior you were seeing.这会导致您看到的奇怪行为。

Below I show 3 different techniques that will allow you to get your desired results, in order of complexity.下面我展示了 3 种不同的技术,它们可以让您按照复杂性的顺序获得所需的结果。 Each has its applications outside of this specific example.在这个特定示例之外,每个都有其应用。

The last ( label3() ) will be the most reliable for longer forms.最后一个( label3() )对于更长的 forms 将是最可靠的。


struct ContentView: View {
    @State var sizes: [String:CGSize] = [:]

    var body: some View {
        VStack {
            HStack(alignment: .firstTextBaseline) {
                self.label3("Endpoint:")
                TextField("https://localhost:8080/api", text: .constant(""))
            }
            Divider()
            HStack(alignment: .firstTextBaseline) {
                self.label3("Path:")
                TextField("/todos", text: .constant(""))
            }
        }
        .padding()
        .onPreferenceChange(SizePreferenceKey.self) { preferences in
            self.sizes = preferences
        }
    }

    func label1(_ text: String) -> some View {
        Text(text) // Use a minimum size based on your best guess.  Look around and you'll see that many macOS apps actually lay forms out like this because it's simple to implement.
            .frame(minWidth: 100, alignment: .trailing)
    }

    func label2(_ text: String, sizer: String = "Endpoint:") -> some View {
        ZStack(alignment: .trailing) { // Use dummy content for sizing based on the largest expected item.  This can be great when laying out icons and you know ahead of time which will be the biggest.
            Text(sizer).opacity(0.0)
            Text(text)
        }
    }

    func label3(_ text: String) -> some View {
        Text(text) // Use preferences and save the size of each label
        .background(
            GeometryReader { proxy in
                Color.clear
                    .preference(key: SizePreferenceKey.self, value: [text : proxy.size])
            }
        )
        .frame(minWidth: self.sizes.values.map { $0.width }.max() ?? 0.0, alignment: .trailing)
    }
}

struct SizePreferenceKey: PreferenceKey {
    typealias Value = [String:CGSize]
    static var defaultValue: Value = [:]

    static func reduce(value: inout Value, nextValue: () -> Value) {
        let next = nextValue()
        for (k, v) in next {
            value[k] = v
        }
    }
}

Here's a screenshot of the results with label2 or label3 .这是label2label3结果的屏幕截图。 两个标签对齐

Using XCode 13.1 and targeting MacOS 12 you can achieve the desired result quite easily by adding a "Form" element:使用 XCode 13.1 并以 MacOS 12 为目标,您可以通过添加“表单”元素轻松实现所需的结果:

struct Canvas: PreviewProvider {
    static var previews: some View {
        Form {
            TextField("Endpoint:", text: .constant(""))
            Divider()
            TextField("Path:", text: .constant(""))
        }
            .previewLayout(.fixed(width: 280, height: 200))
    }
}

The divider is not covering the area of the labels, but this is intended by Apple.分隔线没有覆盖标签的区域,但这是 Apple 设计的。 Also, I haven't found out quickly how to add the placeholders to the text fields.此外,我还没有快速找到如何将占位符添加到文本字段中。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM