如何在 AppKit 上的自定义 SwiftUI 表单中右对齐项目标签?
How to right-align item labels in a custom SwiftUI form on AppKit?
我有以下 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))
}
}
这个面板看起来不错,但我想右对齐“端点:”和“路径:”标签:
所以我应用了自定义水平对齐方式:
struct Canvas: PreviewProvider {
static var previews: some View {
VStack(alignment: .label) {
HStack(alignment: .firstTextBaseline) {
Text("Endpoint:").alignmentGuide(.label) { [=11=][.trailing] }
TextField("https://localhost:8080/api", text: .constant(""))
}
Divider()
HStack(alignment: .firstTextBaseline) {
Text("Path:").alignmentGuide(.label) { [=11=][.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)
}
结果不是我需要的:
没有文档,请帮忙
我认为对齐指南在当前实施中不会在这里起作用。在和他们玩了一会儿之后,他们似乎根据容器的给定大小 调整 children 的大小,然后 根据指南对齐每个 child。这会导致您看到的奇怪行为。
下面我展示了 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 { [=10=].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
}
}
}
使用 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 的意图。另外,我还没有很快找到如何将占位符添加到文本字段的方法。
我有以下 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))
}
}
这个面板看起来不错,但我想右对齐“端点:”和“路径:”标签:
所以我应用了自定义水平对齐方式:
struct Canvas: PreviewProvider {
static var previews: some View {
VStack(alignment: .label) {
HStack(alignment: .firstTextBaseline) {
Text("Endpoint:").alignmentGuide(.label) { [=11=][.trailing] }
TextField("https://localhost:8080/api", text: .constant(""))
}
Divider()
HStack(alignment: .firstTextBaseline) {
Text("Path:").alignmentGuide(.label) { [=11=][.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)
}
结果不是我需要的:
没有文档,请帮忙
我认为对齐指南在当前实施中不会在这里起作用。在和他们玩了一会儿之后,他们似乎根据容器的给定大小 调整 children 的大小,然后 根据指南对齐每个 child。这会导致您看到的奇怪行为。
下面我展示了 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 { [=10=].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
}
}
}
使用 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 的意图。另外,我还没有很快找到如何将占位符添加到文本字段的方法。