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

如何修复UICollectionView长按UIContextMenu长文本截断问题

问题根因

UIAction 标题是系统原生封装的单行文本组件,默认强制单行显示、超长尾部截断,没有公开API可以修改这个默认行为,直接把长文本传入UIAction的title参数,无论怎么设置外部属性,都会被系统截断。

修复方案

不要把完整长文本放在UIAction的标题位,优先用上下文菜单的自定义预览区承载长文本展示,和原生交互逻辑一致,不会出现截断问题。

方案1:自定义预览区展示完整文本(推荐)

给UIContextMenuConfiguration传入previewProvider,返回一个支持多行文本的自定义控制器承载完整内容,菜单区只保留实际操作项(比如复制)即可,代码实现如下:

func configureContextMenu(text: String) -> UIContextMenuConfiguration {
    // 预览区提供器:专门展示完整长文本
    let previewProvider: () -> UIViewController? = {
        let previewVC = UIViewController()
        // 计算文本实际高度,适配内容尺寸
        let contentMaxWidth = UIScreen.main.bounds.width - 60
        let textBoundingSize = CGSize(width: contentMaxWidth - 40, height: .greatestFiniteMagnitude)
        let textHeight = text.boundingRect(
            with: textBoundingSize,
            options: .usesLineFragmentOrigin,
            attributes: [.font: UIFont.systemFont(ofSize: 16)],
            context: nil
        ).height
        // 限制预览区最大高度,超长内容可后续替换为UITextView支持滚动
        let previewHeight = min(textHeight + 40, 300)
        previewVC.preferredContentSize = CGSize(width: contentMaxWidth, height: previewHeight)
        
        let contentLabel = UILabel(frame: CGRect(x: 20, y: 20, width: contentMaxWidth - 40, height: textHeight))
        contentLabel.text = text
        contentLabel.numberOfLines = 0
        contentLabel.font = .systemFont(ofSize: 16)
        previewVC.view.addSubview(contentLabel)
        previewVC.view.backgroundColor = .secondarySystemBackground
        return previewVC
    }
    
    return UIContextMenuConfiguration(identifier: nil, previewProvider: previewProvider) { _ in
        // 菜单区只放操作项,不承载长文本展示
        let copyAction = UIAction(title: "复制文本", image: UIImage(systemName: "doc.on.doc")) { _ in
            UIPasteboard.general.string = text
        }
        return UIMenu(title: "", children: [copyAction])
    }
}

方案2:拆分长文本为多个菜单项(无预览场景用)

如果不想使用自定义预览,可以把长文本按屏幕可展示的单行长拆分为多个短片段,每个片段生成一个不可交互的UIAction做展示,最后追加操作项即可,适合文本长度中等的场景:

// 按指定长度拆分长文本
private func splitText(_ text: String, perChunkLength: Int = 22) -> [String] {
    guard text.count > perChunkLength else { return [text] }
    var chunks: [String] = []
    for idx in stride(from: 0, to: text.count, by: perChunkLength) {
        let startPos = text.index(text.startIndex, offsetBy: idx)
        let endPos = text.index(startPos, offsetBy: min(perChunkLength, text.count - idx))
        chunks.append(String(text[startPos..<endPos]))
    }
    return chunks
}

func configureContextMenu(text: String) -> UIContextMenuConfiguration {
    return UIContextMenuConfiguration(identifier: nil, previewProvider: nil) { _ in
        let textChunks = splitText(text)
        var menuItems: [UIAction] = []
        // 生成长文本展示项,标记为不可点击
        for chunk in textChunks {
            let displayItem = UIAction(title: chunk, attributes: .disabled) { _ in }
            menuItems.append(displayItem)
        }
        // 追加操作项
        let copyItem = UIAction(title: "复制文本", image: UIImage(systemName: "doc.on.doc")) { _ in
            UIPasteboard.general.string = text
        }
        menuItems.append(copyItem)
        return UIMenu(title: "", children: menuItems)
    }
}
避坑提示
  • 不要通过runtime修改UIAction内部的UILabel属性,存在App Store审核风险,且不同iOS版本内部实现有变动,稳定性无法保证
  • 自定义预览时必须设置preferredContentSize,否则系统会使用默认预览尺寸,依然会出现内容展示不全的问题
  • 如果要展示的文本超过300字,可以把预览里的UILabel替换为UITextView,支持滚动浏览,体验更好

内容的提问来源于stack exchange,提问作者Dracarys

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.31 21:57:23