Originally I just passed a label to the actual TextField but could not get the desired effect.
This is the solution I'm using but I find it unsatisfactory. I wrap each form item in LabeledContent. It does not look as tidy as the Form label formatting but without it when I use .alignmentGuide it operates from a unique origin which depends on the label but simply adjusting for the label length does not give consistent results.
Form {
LabeledContent {
CustomField(
thing: $activeThing,
setValue: { thingtype, newValue in thingtype.thing = newValue }
)
} label: {
Text(shortLabel)
.frame(width: longLabelWidth, alignment: .trailing)
}
CustomField(
thing: $activeThing,
setValue: { thingtype, newValue in thingtype.thing = newValue }
)
} label: {
Text(longLabel)
.frame(width: longLabelWidth, alignment: .trailing)
}
}
struct CustomField: View {
...
var body: some View {
TextField("", text: $displayText)
.inlineCompletion(
text: displayText,
completion: completion,
)
.onAppear {displayText = getValue(transaction)}
struct InlineCompletion: ViewModifier {
let text: String
let completion: String
func body(content: Content) -> some View {
ZStack(alignment: .leading) {
content
if !completion.isEmpty {
Text(completion)
.foregroundColor(.secondary)
.opacity(text.isEmpty ? 0 : 0.7)
.alignmentGuide(.leading) { _ in
-textWidth(text) - baseFontSize
}
}
}
}
}
extension View {
func inlineCompletion(text: String, completion: String) -> some View {
modifier(
InlineCompletion(
text: text,
completion: completion
)
)
}
}