如何修复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
相关产品推荐
相关产品推荐

