Hey there.
I’m trying add a custom build input (like TextField) to a Form, archiving the same behaviour like native SwiftUI fields: All labels are aligned to the right.
Is there such way to archive that with a HStack { Text("label") InputView() }
?
iOS 16
You can use the new Grid
API with a custom alignment for the labels.
Grid(alignment: .leadingFirstTextBaseline) {
GridRow {
Text("Username:")
.gridColumnAlignment(.trailing) // align the entire first column
TextField("Enter username", text: $username)
}
GridRow {
Label("Password:", systemImage: "lock.fill")
SecureField("Enter password", text: $password)
}
GridRow {
Color.clear
.gridCellUnsizedAxes([.vertical, .horizontal])
Toggle("Show password", isOn: $showingPassword)
}
}
iOS 15 and earlier
You can achieve this through the use of custom alignment guides and a custom view that wraps up the functionality for each row.
extension HorizontalAlignment {
private struct CentredForm: AlignmentID {
static func defaultValue(in context: ViewDimensions) -> CGFloat {
context[HorizontalAlignment.center]
}
}
static let centredForm = Self(CentredForm.self)
}
struct Row<Label: View, Content: View> {
private let label: Label
private let content: Content
init(@ViewBuilder content: () -> Content, @ViewBuilder label: () -> Label) {
self.label = label
self.content = content()
}
init(@ViewBuilder content: () -> Content) where Label == EmptyView {
self.init(content: content) { EmptyView() }
}
init(_ titleKey: LocalizedStringKey, @ViewBuilder content: () -> Content) where Label == Text {
self.init(content: content) { Text(titleKey) }
}
init<S: StringProtocol>(_ title: S, @ViewBuilder content: () -> Content) where Label == Text {
self.init(content: content) { Text(title) }
}
var body: some View {
HStack {
label.alignmentGuide(.centredForm) { $0[.trailing] }
content.alignmentGuide(.centredForm) { $0[.leading] }
}
}
}
The multiple initialisers are there for convenience and taken from the standard SwiftUI controls. Fell free to remove the ones you don't use.
It can then be implemented like this:
// need to have the alignment parameter for it to work
VStack(alignment: .centredForm) {
// with text label
Row("Username:") {
TextField("Enter username", text: $username)
}
// with view label
Row {
SecureField("Enter password", text: $password)
} label: {
Label("Password:", systemImage: "lock.fill")
}
// without label but still aligned correctly
Row {
Toggle("Show password", isOn: $showingPassword)
}
}
Obviously, place your own views in where they need to go. Both solutions will work, just choose the one you want to use (bearing in mind target version).