Home > Software engineering >  SwiftUi how to prevent text to automatically truncate in order to fit
SwiftUi how to prevent text to automatically truncate in order to fit

Time:10-23

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.

  • Related