I am attempting to achieve a layout within an HStack
that contains two views: A Text
view that may or may not contain multiple lines of text, and a Button
. These are aligned with the top edge of the containing HStack
.
For the purpose of this question, the Button
could be any kind of View
.
I want the height of the Button
to be equal to the height of the first line of the text contained within the Text
element.
This is easy when the Text
contains a single line - there are obvious ways to make two views equally tall. But it's unclear to me how to do this when the Text
contains multiple lines of text when the size of the text could differ based on accessibility settings, etc.
I know how to do this in UIKit, but am unsure of how to do it with SwiftUI. Does anybody have any ideas? Thanks!
I've included a screenshot showing more or less what I'm going for. In this example I've hardcoded the height of the Button
(so it looks pretty close), but this won't work if the Text
's font size changes for any reason.
Edit 1: What I've Tried
Not a whole lot. I think the solution hinges on being able to get the metrics of the font associated with the text displayed by the Text
, but it's not clear how to do so in SwiftUI. This font could come from anywhere, be derived from a Font.TextStyle
, and is subject to the user's Dynamic Type setting.
My searching for this has turned up nothing so far, hence my question here.
Edit 2: Code Sample
Below is code from my simplified hardcoded implementation. Here I (roughly) estimate the size of a .title3
TextStyle
to manually set the height of the Button
, but this is intended to be a generalized solution. So, when this is put in production the font could be any size (either derived from a TextStyle
or via some sort of system/custom font).
HStack(alignment: .top, spacing: 4.0, content: {
Text(title)
.font(.title3)
.fontWeight(.medium)
.frame(maxWidth: .infinity, alignment: .leading)
.border(Color.gray)
Button(action: {
}, label: {
Text("See More")
.font(.caption)
})
.frame(minHeight: 26.0, alignment: .center)
.border(Color.gray)
})
This can be solved by using a hidden placeholder to establish the footprint for the button.
maxHeight: .infinity
so that it uses the full height of the footprint.struct ContentView: View {
let title = "Distinctio eligendi maxime non officia aut ratione deliti. Et aliquid maiores adipisci."
private var theButton: some View {
Button("See More") {}
.font(.caption)
}
private var buttonFootprint: some View {
ZStack {
Text("X")
.font(.title3)
.fontWeight(.medium)
theButton
.disabled(true)
}
.hidden()
}
var body: some View {
HStack(alignment: .top) {
Text(title)
.font(.title3)
.fontWeight(.medium)
.frame(maxWidth: .infinity, alignment: .leading)
.border(.gray)
buttonFootprint
.overlay {
theButton
.frame(maxHeight: .infinity)
.border(.gray)
}
}
}
}
An alternative approach would be to compute the height of 1 line of text using the lineHeight
delivered by a UIFont
. A ScaledMetric
can be used to adapt to dynamic font sizes, my answer to How do I have achieve a combined line limit for two Text views in a VStack in SwiftUI? shows how.