简体   繁体   English

SwiftUI 获取显示的字符数量

[英]SwiftUI get characters amount which are displayed

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).如果完整列表不适合 3 行,则应截断该列表并将“+ X others”添加到末尾,其中 X 是被截断的提供程序的数量(未显示)。
example of a 3rd line: "Name, Name, Name, Name, Name + 4 others"第 3 行示例:“姓名、姓名、姓名、姓名、姓名、姓名 + 4 个其他人”

在此处输入图像描述

I've seen an example with ExpandableView which get view size of with with Geometry but I don't get how calculate amount of characters which can fit to that size.我已经看到了一个使用ExpandableView的示例,该示例使用Geometry获取视图大小,但我不知道如何计算适合该大小的字符数量。

It's not a perfect solution and could be refactored but is works for both cases.这不是一个完美的解决方案,可以重构,但适用于这两种情况。

if more than 3 lines:如果超过 3 行:

在此处输入图像描述

less than 3 lines:少于 3 行:

在此处输入图像描述

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
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM