【问题标题】:How to right-align item labels in a custom SwiftUI form on AppKit?如何在 AppKit 上的自定义 SwiftUI 表单中右对齐项目标签?
【发布时间】:2019-10-19 12:14:11
【问题描述】:

我有以下可可形式:

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))
  }
}

这个面板看起来不错,但我想右对齐“端点:”和“路径:”标签:

所以我应用了自定义水平对齐方式:

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)
}

结果不是我需要的:

没有文档,请帮忙。

【问题讨论】:

    标签: forms macos cocoa swiftui appkit


    【解决方案1】:

    我认为对齐指南不会在当前的实施中发挥作用。在和他们玩了一会儿之后,他们似乎根据容器的给定大小调整了孩子的大小然后根据指南对齐每个孩子。这会导致您看到的奇怪行为。

    下面我展示了 3 种不同的技术,它们可以让您获得所需的结果,按复杂程度排列。在这个特定示例之外,每个都有其应用程序。

    最后一个 (label3()) 对于较长的表单最可靠。

    
    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
            }
        }
    }
    

    这是label2label3 的结果截图。

    【讨论】:

    • 好吧,我更喜欢.label1(),但是有没有办法使用容器宽度的比率来定义最大尺寸? ?
    • 您可以使用GeometryReader。我建议使用我在label3() 中说明的相同技术,将GeometryReader 放在背景中并保存@State var contentSize: CGSize = .zero.frame(maxWidth: self.contentSize.width * 0.25, alignment: .trailing) 之类的大小。这样,您的视图将继续以固有尺寸正确布局。 GeometryReader 本身总是会扩展以适应其全部可用区域,就像 Color 一样。
    • 这有帮助。但事实证明,HStack 默认使用 50%,这适用于 99% 的情况。 .label3() 方法的一个小问题是异步计算,因此它会在微小的延迟之后跳转到正确的位置。我现在会选择.label1()!再次感谢?
    【解决方案2】:

    使用 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))
        }
    }
    

    分隔线未覆盖标签区域,但这是 Apple 设计的。另外,我还没有快速找到如何将占位符添加到文本字段。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-03-03
      • 2016-11-15
      • 2022-11-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-10-01
      相关资源
      最近更新 更多