You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.02 21:03:41