Как правильно выровнять метки элементов в пользовательской форме SwiftUI в AppKit? - PullRequest
6 голосов
/ 19 октября 2019

У меня есть следующая форма Какао:

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 Ответ

1 голос
/ 23 октября 2019

Я не верю, что направляющие выравнивания будут работать здесь в их текущей реализации. Немного поиграв с ними, кажется, что они определяют размер своих дочерних элементов в соответствии с заданным размером контейнера , а затем выравнивают каждого дочернего элемента на основе руководства. Это приводит к странному поведению, которое вы видели.

Ниже я покажу 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
        }
    }
}

Вот скриншот результатов с label2 или label3. The two labels are aligned

...