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

Posted

技术标签:

【中文标题】如何在 AppKit 上的自定义 SwiftUI 表单中右对齐项目标签?【英文标题】:How to right-align item labels in a custom SwiftUI form on AppKit? 【发布时间】: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)

结果不是我需要的:

没有文档,请帮忙。

【问题讨论】:

【参考方案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 设计的。另外,我还没有快速找到如何将占位符添加到文本字段。

【讨论】:

以上是关于如何在 AppKit 上的自定义 SwiftUI 表单中右对齐项目标签?的主要内容,如果未能解决你的问题,请参考以下文章

AppKit 上的 SwiftUI - 在弹出窗口中将焦点设置为 TextField

Mac 上的 SwiftUI - 如何将按钮指定为主按钮?

将 AppKit/UIKit 中的 Key-Value Observation 转换为 Combine 和 SwiftUI

SwiftUI 按钮,在 AppKit 中扩展并接受键盘快捷键

从 AppKit NSButton 打开新的 SwiftUI 窗口

如何将我的自定义 ButtonStyle 添加到 swiftui3 中的快捷方式 swiftui .buttonstyle() 属性