我正在尝试在 SwiftUI
HStack
中包含自定义 Form
行,如下所示:
var body: some View {
Form {
TextField("Text", text: .constant("test"))
Toggle("Toggle", isOn: .constant(true))
.toggleStyle(SwitchToggleStyle())
HStack {
Text("Label")
MenuButton("Menu") {
Button(action: {
print("Clicked Pizza")
}) { Text("Pizza") }
Button(action: {
print("Clicked Pasta")
}) { Text("Pasta") }
}
TextField("Topping", text: .constant("Cheese"))
.labelsHidden()
}
}
.padding()
}
导致
但是,我希望
Label
与 Toggle
垂直对齐,并且 Menu
与切换开关垂直对齐。
是否有为自定义
HStack
行选择对齐模式的标准方法?
LabeledContent
视图将为您提供帮助:
LabeledContent("Label") {
MenuButton("Menu") {
Button(action: {
print("Clicked Pizza")
}) { Text("Pizza") }
Button(action: {
print("Clicked Pasta")
}) { Text("Pasta") }
}
TextField("Topping", text: .constant("Cheese"))
.labelsHidden()
}
}
但是,此视图尚未向后移植到早期版本的 macOS,因此如果您需要支持早期版本,则需要另一种方法。
基于@Nhat Nguyen Duc 的首选项键代码,关键是使用对齐指南而不是填充。创建自定义视图,并使用仅测量宽度的自定义首选项:
struct LabeledHStack<Content: View>: View {
var label: String
var content: () -> Content
@State var labelWidth: CGFloat = 0
init(_ label: String, @ViewBuilder content: @escaping () -> Content) {
self.label = label
self.content = content
}
var body: some View {
HStack {
Text(label)
.readWidth { self.labelWidth = $0 }
content()
}
.alignmentGuide(.leading) { _ in labelWidth + 10 } // see note
}
}
struct WidthPreferenceKey: PreferenceKey {
static var defaultValue: CGFloat = 0
static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) { }
}
extension View {
func readWidth(onChange: @escaping (CGFloat) -> Void) -> some View {
background(
GeometryReader { geometryProxy in
Color.clear
.preference(key: WidthPreferenceKey.self, value: geometryProxy.size.width)
}
)
.onPreferenceChange(WidthPreferenceKey.self, perform: onChange)
}
}
请注意,在自定义视图中,我添加了 10 个像素来快速模拟标签与其表单元素之间的间距。可能有更好的方法来使其适用于可访问性大小等(例如,使用
@ScaledMetric
值)。您可能还希望将其应用为填充,而不是在对齐指南计算中。
下面有一行 macOS13 的
LabeledContent
,后面是 LabeledHStack
:
LabeledContent {
HStack {
// ...
}
} label: {
Text("Count")
}
LabeledContent
这里想法:使用GeometryReader计算标签的大小,并按其宽度偏移视图。
@State private var textSize = CGSize.zero
var body: some View {
Form {
TextField("Text", text: .constant("test"))
.padding(.leading, -textSize.width)
Toggle("Toggle", isOn: .constant(true))
.toggleStyle(SwitchToggleStyle())
.padding(.leading, -textSize.width)
HStack {
Text("Label")
.readSize { textSize in
self.textSize = textSize
}
MenuButton("Menu") {
Button("Pizza") {
print("Clicked Pizza")
}
Button("Pasta") {
print("Clicked Pasta")
}
}
TextField("Topping", text: .constant("Cheese"))
.labelsHidden()
}
.padding(.leading, -textSize.width - 10)
.frame(maxWidth: .infinity)
}
.padding(.leading, textSize.width + 10)
.padding()
}
extension View
extension View {
func readSize(onChange: @escaping (CGSize) -> Void) -> some View {
background(
GeometryReader { geometryProxy in
Color.clear
.preference(key: SizePreferenceKey.self, value: geometryProxy.size)
}
)
.onPreferenceChange(SizePreferenceKey.self, perform: onChange)
}
}
struct SizePreferenceKey
struct SizePreferenceKey: PreferenceKey {
static var defaultValue: CGSize = .zero
static func reduce(value: inout CGSize, nextValue: () -> CGSize) { }
}
Button(<#String#>) { <#Action#> }
而不是
Button(action: { <#Action#> }) { Text(<#String#>) }
您可以将内容包装在 VStack 中,并使用其
alignment
修饰符将所有内容与前导对齐,例如:
VStack(对齐:.leading)
像这样:
var body: some View {
Form {
VStack (alignment: .leading){
TextField("Text", text: .constant("test"))
Toggle("Toggle", isOn: .constant(true))
.toggleStyle(SwitchToggleStyle())
HStack {
Text("Label")
MenuButton("Menu") {
Button(action: {
print("Clicked Pizza")
}) { Text("Pizza") }
Button(action: {
print("Clicked Pasta")
}) { Text("Pasta") }
}
TextField("Topping", text: .constant("Cheese"))
.labelsHidden()
}
.frame(width: .infinity, height: .infinity)
}
}
.padding()
}
请参阅此处的工作示例: