I have an hstack with multiple items inside, when the first text is too long it gets truncated and also the other elements in the line, so elements are still in the hstack but with a weird aligment, I've posted a screenshot of the issue and what I'd like to achieve. I'd need that if the username is too long and it doesn't fit it should make a new line with the other elements.
[![This is what happens now][1]][1]
[![This is what I'd need][2]][2]
HStack {
Text("longtext username test")
// Block 1
Text(Image(systemName: "network"))
Text("12345")
// Block 2
Text(Image(systemName: "network"))
Text("2.0K")
// Block 3
Text(Image(systemName: "network"))
Text("2H")
// Block 4
Text(Image(systemName: "network"))
Text(Image(systemName: "network"))
Text(Image(systemName: "network"))
}
[1]: https://i.stack.imgur.com/Bj1Cb.png
[2]: https://i.stack.imgur.com/wITRf.png
// if the username is short and fit in the line
// Place all items in the same line
short-username text2 text3 text4..
// If the username is too long and doesn't fit
// Make a new line with all the other elements
long-username
text2 text3 test4..
CodePudding user response:
That is a bit more involved. It takes a lot of interplay with PreferenceKeys
. This code could probably be shortened, but it works...
struct InlineTextView: View {
@State var userName = "longtext username test"
@State var userNamePref: CGFloat = 0
@State var additionalTextPref: CGFloat = 0
@State var hStackPref: CGFloat = 0
@State var totalWidth: CGFloat = 0
@State var isTooWide: Bool = true
var body: some View {
GeometryReader {outerGeometry in
VStack(alignment: .leading, spacing: 20) {
Group {
if isTooWide {
Text(userName)
.background(GeometryReader { userGeometry in
Color.clear.preference(
key: UsernameWidthPreferenceKey.self,
value: userGeometry.size.width)
})
additionalInlineText
.onTapGesture {
print("tapped")
}
.background(GeometryReader { addGeometry in
Color.clear.preference(
key: AdditionalWidthPreferenceKey.self,
value: addGeometry.size.width)
})
} else {
HStack {
Text(userName)
.background(GeometryReader { geometry in
Color.clear.preference(
key: UsernameWidthPreferenceKey.self,
value: geometry.size.width)
})
additionalInlineTextView
.background(GeometryReader { geometry in
Color.clear.preference(
key: AdditionalWidthPreferenceKey.self,
value: geometry.size.width)
})
}
}
}
.onPreferenceChange(UsernameWidthPreferenceKey.self) {
userNamePref = max($0, userNamePref)
totalWidth = userNamePref additionalTextPref
isTooWide = totalWidth > outerGeometry.size.width
print("userNamePref = \(userNamePref)")
print("additionalTextPref = \(additionalTextPref)")
print("hStackPref = \(hStackPref)")
print("totalWidth = \(totalWidth)")
print("outerGeo = \(outerGeometry.size.width)")
}
.onPreferenceChange(AdditionalWidthPreferenceKey.self) {
additionalTextPref = max($0, additionalTextPref)
totalWidth = userNamePref additionalTextPref
isTooWide = totalWidth > outerGeometry.size.width
print("userNamePref = \(userNamePref)")
print("additionalTextPref = \(additionalTextPref)")
print("hStackPref = \(hStackPref)")
print("totalWidth = \(totalWidth)")
print("outerGeo = \(outerGeometry.size.width)")
}
Button {
if userName == "longtext username test" {
userNamePref = 0
userName = "userName"
} else {
userNamePref = 0
userName = "longtext username test"
}
} label: {
Text("Change name.")
}
}
}
}
var additionalInlineText: Text {
// Block 1
Text(Image(systemName: "network"))
Text("12345")
// Block 2
Text(Image(systemName: "network"))
Text("2.0K")
// Block 3
Text(Image(systemName: "network"))
Text("2H")
// Block 4
Text(Image(systemName: "network"))
Text(Image(systemName: "network"))
Text(Image(systemName: "network"))
Text("1234")
}
}
private extension InlineTextView {
struct UsernameWidthPreferenceKey: PreferenceKey {
static let defaultValue: CGFloat = 0
static func reduce(value: inout CGFloat,
nextValue: () -> CGFloat) {
value = max(value, nextValue())
}
}
struct AdditionalWidthPreferenceKey: PreferenceKey {
static let defaultValue: CGFloat = 0
static func reduce(value: inout CGFloat,
nextValue: () -> CGFloat) {
value = max(value, nextValue())
}
}
}
Edit: made additionalInlineText tappable and changed the name to make it clear that it was a text.
And yes, the GeometryReader
determines the width of the screen, and the preference keys determine the width of the views. I start them in the VStack
so they read their max width. If you notice when userName
is changed, I reset the userNamePref
to get a new reading. This is a bit fragile, but it is the only way to do what you want.