Home > Enterprise >  SwiftUI get characters amount which are displayed
SwiftUI get characters amount which are displayed

Time:07-25

I have a list of providers which I displayed on view.

If the complete list will not fit on 3 lines, the list should be truncated and " X others" added to the end, whereby X is the number of providers that are truncated (not shown).
example of a 3rd line: "Name, Name, Name, Name, Name 4 others"

enter image description here

I've seen an example with enter image description here

less than 3 lines:

enter image description here

import SwiftUI

struct ContentView: View {
    let lineLimit: CGFloat = 3
    @State var isTruncated: Bool = false
    @State var size: CGSize = .zero
    
    @State var value: String = "Provider 1, Provider 2, Provider 3, Provider 4, Provider 5, Provider 6, Provider 7, Provider 8, Provider 9, Provider 10, Provider 11, Provider 12, Provider 13, Provider 14, Provider 15, Provider 16, Provider 17, Provider 18, Provider 19, Provider 20, Provider 21, Provider 22, Provider 23, Provider 24, Provider 25, Provider 26, Provider 27, Provider 28, Provider 29, Provider 30, Provider 31, Provider 32, Provider 33, Provider 34, Provider 35, Provider 36, Provider 37, Provider 38, Provider 39, Provider 40, Provider 41, Provider 42, Provider 43, Provider 44, Provider 45, Provider 46, Provider 47, Provider 48, Provider 49, Provider 50, Provider 51, Provider 52, Provider 53, Provider 54, Provider 55, Provider 56, Provider 57, Provider 58, Provider 59, Provider 60, Provider 61, Provider 62, Provider 63, Provider 64, Provider 65, Provider 66, Provider 67, Provider 68, Provider 69, Provider 70, Provider 71, Provider 72, Provider 73, Provider 74, Provider 75, Provider 76, Provider 77, Provider 78, Provider 79, Provider 80"
    
    @State var visibleProviders: String = ""
    
    var body: some View {
        VStack {
            if isTruncated {
                Text(visibleProviders)
                
            } else {
                TruncableText(
                    text: Text(value),
                    lineLimit: Int(lineLimit)
                ) { (isTruncated, size) in
                    self.isTruncated = isTruncated
                    self.size = size
                    self.calcSize(size: size)
                }
            }
            
        }
        .padding()
    }

    func calcSize(size: CGSize) {
        
        var providers = value.components(separatedBy: ", ")
        var text: String = ""
        var textSize: CGFloat {
            text.widthOfString(usingFont: .preferredFont(forTextStyle: .headline))/lineLimit
        }
        var otherProvidersString: String {
            "   \(providers.count) others"
        }
        var otherProvidersStringSize: CGFloat {
             otherProvidersString.widthOfString(usingFont: .preferredFont(forTextStyle: .headline))/lineLimit
        }
        
        var valueSize: CGFloat {
            value.widthOfString(usingFont: .preferredFont(forTextStyle: .headline))/lineLimit
        }
        
        var isSizeLess: Bool {
            size.width > (textSize   otherProvidersStringSize) && valueSize > size.width
        }
        
        while isSizeLess {
            if !providers.isEmpty, let firstProvider = providers.first {
                text  = firstProvider
                providers.removeFirst()
            }
            if isSizeLess {
                text  = ", "
        }
        }
        
        visibleProviders = text   otherProvidersString
    }
    
}

struct TruncableText: View {
    let text: Text
    let lineLimit: Int?
    @State private var intrinsicSize: CGSize = .zero
    @State private var truncatedSize: CGSize = .zero
    let isTruncatedUpdate: (_ isTruncated: Bool, _ size: CGSize) -> Void
    
    var body: some View {
        text
            .lineLimit(lineLimit)
            .readSize { size in
                truncatedSize = size
                isTruncatedUpdate(truncatedSize != intrinsicSize, size)
            }
            .background(
                text
                    .fixedSize(horizontal: false, vertical: true)
                    .hidden()
                    .readSize { size in
                        intrinsicSize = size
                        isTruncatedUpdate(truncatedSize != intrinsicSize, size)
                    }
            )
    }
}

public extension View {
    func readSize(onChange: @escaping (CGSize) -> Void) -> some View {
        background(
            GeometryReader { geometryProxy in
                Color.clear
                    .preference(key: SizePreferenceKey.self, value: geometryProxy.size)
            }
        )
        .onPreferenceChange(SizePreferenceKey.self, perform: onChange)
    }
}

private struct SizePreferenceKey: PreferenceKey {
    static var defaultValue: CGSize = .zero
    static func reduce(value: inout CGSize, nextValue: () -> CGSize) {}
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

extension String {
    func widthOfString(usingFont font: UIFont) -> CGFloat {
        let fontAttributes = [NSAttributedString.Key.font: font]
        let size = self.size(withAttributes: fontAttributes)
        return size.width
    }
}
  • Related