Swift实现逗号分隔字符串截断并显示剩余项数的方法
逗号分隔列表动态截断带剩余计数实现方案
需求为:将字符串数组拼接为逗号分隔的文本,当文本总长度超出当前可用宽度时截断,末尾追加, & n more...格式的提示,n为剩余未展示的项数,预期效果如下:
原生能力说明
SwiftUI 系统提供的truncationMode修饰符仅支持基础的截断省略效果,可配置省略号出现在头部/中间/尾部,但没有原生内置修饰符可以直接实现带剩余项数的自定义截断后缀,需要自行实现适配逻辑。
原有代码的缺陷
- 硬编码最多取前2项进行拼接,完全无法适配iPad等大屏设备的宽屏场景,即使有多余空间可以展示更多项也会被强制截断
- 长度计算逻辑不严谨:仅统计了前1/2项的字符数,没有计入分隔符
,、后缀提示本身占用的长度,很容易出现计算结果和实际渲染长度不符、文本溢出的问题 - 用字符数
count判断长度的方式不准确:不同字体、不同字符(如中文、emoji、粗体字)的实际渲染宽度差异极大,单纯数个数无法匹配真实排版结果
重构实现方案
核心思路:放弃硬编码展示项数的逻辑,基于当前可用的实际渲染宽度、文本使用的字体,逐个累加列表项计算宽度,直到剩余空间不足以放下下一项+后缀提示时停止,再拼接最终结果。
1. 字符串渲染宽度计算扩展
import SwiftUI extension String { /// 计算字符串在指定字体下的实际渲染宽度 func renderWidth(font: UIFont) -> CGFloat { let attr = [NSAttributedString.Key.font: font] return self.size(withAttributes: attr).width } }
2. 动态拼接核心方法
/// 拼接带动态截断的逗号分隔列表 /// - Parameters: /// - items: 待拼接的字符串数组 /// - maxWidth: 文本可渲染的最大宽度 /// - font: 文本使用的字体,默认系统17号常规字体 /// - Returns: 拼接完成的字符串 func formatTruncatedList( items: [String], maxWidth: CGFloat, font: UIFont = .systemFont(ofSize: 17) ) -> String { // 安全兜底 guard !items.isEmpty else { return "" } let separator = ", " var displayed: [String] = [] var currentWidth: CGFloat = 0 for (index, item) in items.enumerated() { // 计算加入当前项需要的额外宽度:非首项需要加分隔符宽度 let separatorWidth = index == 0 ? 0 : separator.renderWidth(font: font) let itemTotalWidth = separatorWidth + item.renderWidth(font: font) // 计算如果加入当前项,需要预留的后缀宽度 let remainCount = items.count - (displayed.count + 1) let suffix = remainCount > 0 ? ", & \(remainCount) more..." : "" let suffixWidth = suffix.renderWidth(font: font) // 总宽度不超限则加入当前项 if currentWidth + itemTotalWidth + suffixWidth <= maxWidth { displayed.append(item) currentWidth += itemTotalWidth } else { break } } // 边界兜底:如果第一项就超宽,至少展示第一项加全量剩余后缀 if displayed.isEmpty { let first = items[0] let remain = items.count - 1 return remain > 0 ? "\(first), & \(remain) more..." : first } // 拼接最终结果 let joinedStr = displayed.joined(separator: separator) let finalRemain = items.count - displayed.count return finalRemain > 0 ? "\(joinedStr), & \(finalRemain) more..." : joinedStr }
3. 视图层动态获取可用宽度
不要写死长度阈值,通过GeometryReader读取当前文本的实际可用宽度,自动适配横竖屏切换、iPad分屏、动态字体大小等场景:
struct DemoView: View { let demoItems = ["Update my profile", "Delete me", "Approve these letters", "Send feedback", "Rate app"] @State private var textMaxWidth: CGFloat = 0 var body: some View { Text(formatTruncatedList(items: demoItems, maxWidth: textMaxWidth)) .font(.system(size: 17)) .padding(.horizontal, 16) .background( GeometryReader { geo in Color.clear .onAppear { textMaxWidth = geo.size.width } .onChange(of: geo.size.width) { newWidth in textMaxWidth = newWidth } } ) } }
方案优势
- 无硬编码展示项数,大屏自动展示更多可容纳的项,小屏自动减少展示项,完美适配iPhone、iPad全设备
- 基于实际字体渲染宽度计算,排版准确不会出现文本溢出
- 自动响应所有布局变化:横竖屏切换、分屏模式、动态字体调整都能自动重新计算生成正确的文本
- 边界逻辑完备:单元素、元素超长、全量元素可放下等场景均能正常处理
内容的提问来源于stack exchange,提问作者Sylar
相关产品推荐
相关产品推荐

