Swift 5中UILabel使用NSTextAttachment无法显示完整图片求助
解决Swift中NSTextAttachment在UILabel显示图片过小的问题
问题描述
尝试在Swift 5中使用NSTextAttachment将网络图片展示在UITableViewCell的UILabel(chatNote)中,期望图片以**屏幕宽度75%、高度50%**的尺寸完整显示,但实际仅呈现极小的裁剪片段:
- 期望效果:图片按比例适配目标尺寸,完整填充label区域
- 实际效果:仅显示原图的极小一部分,几乎不可见
- 裁剪细节:仅展示原图的局部小区域
原实现代码
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cellIdentifier = "ChatsAndMessagesChatBoxTableCell" var cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as? ChatsAndMessagesChatBoxTableCell if (cell == nil) { cell = ChatsAndMessagesChatBoxTableCell(style: UITableViewCell.CellStyle.default, reuseIdentifier: cellIdentifier) as ChatsAndMessagesChatBoxTableCell } let pic_url = "https://fastly.picsum.photos/id/0/5000/3333.jpg?hmac=_j6ghY5fCfSD6tvtcV74zXivkJSPIfR9B8w34XeQmvU" URLSession.shared.dataTask(with: NSURL(string: pic_url)! as URL, completionHandler: { (data, response, error) -> Void in if error != nil { print(error ?? "No Error") return } DispatchQueue.main.async(execute: { () -> Void in let image = UIImage(data: data!) // 1. Create an NSTextAttachment let imageAttachment = NSTextAttachment() let screenWidth = UIScreen.main.bounds.width let screenHeight = UIScreen.main.bounds.height let desiredWidth = screenWidth * 0.75 let desiredHeight = screenHeight * 0.5 let imageAspectRatio = image!.size.width / image!.size.height var finalImageWidth: CGFloat = desiredWidth var finalImageHeight: CGFloat = desiredHeight // Calculate final dimensions to fit within the desired ratios if (desiredWidth / desiredHeight) > imageAspectRatio { // Image is wider than the desired ratio, adjust height to fit width finalImageHeight = desiredWidth / imageAspectRatio } else { // Image is taller than or matches the desired ratio, adjust width to fit height finalImageWidth = desiredHeight * imageAspectRatio } imageAttachment.image = image imageAttachment.bounds = CGRect(x: 0, y: ((cell?.chatNote.font.capHeight)! - finalImageHeight) / 2, width: finalImageWidth, height: finalImageHeight) let imageAttributedString = NSAttributedString(attachment: imageAttachment) cell?.chatNote.attributedText = imageAttributedString }) }).resume() return cell! }
问题原因
- UILabel配置缺失:默认
numberOfLines = 1且未设置preferredMaxLayoutWidth,导致label无法自动调整行高适配大尺寸附件,图片被强制裁剪到默认行高范围内。 - bounds的Y轴偏移错误:使用
font.capHeight计算垂直偏移时,该值远小于图片目标高度,导致图片向上偏移后大部分区域被label裁剪。 - cell复用未处理:未取消cell之前的网络请求,可能导致复用cell时显示错误图片。
修复方案
1. 配置UILabel基础属性
确保label支持多行显示并设置最大布局宽度:
cell.chatNote.numberOfLines = 0 let targetLabelWidth = UIScreen.main.bounds.width * 0.75 cell.chatNote.preferredMaxLayoutWidth = targetLabelWidth
2. 修正NSTextAttachment的bounds设置
调整Y轴偏移计算逻辑,让图片垂直居中且完整显示在label内:
imageAttachment.bounds = CGRect( x: 0, y: (cell.chatNote.font.descender - finalHeight) / 2, width: finalWidth, height: finalHeight )
3. 优化网络请求与cell复用
取消cell之前的网络请求,避免复用错误;同时使用weak cell防止循环引用。
4. 开启TableView自动行高
确保tableView能根据内容自动调整行高:
// 在viewDidLoad中设置 tableView.rowHeight = UITableView.automaticDimension tableView.estimatedRowHeight = 200 // 设置合理的预估高度
修复后的完整代码
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cellIdentifier = "ChatsAndMessagesChatBoxTableCell" // 强制转换,确保cell已正确注册 let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! ChatsAndMessagesChatBoxTableCell // 配置UILabel核心属性 let screenWidth = UIScreen.main.bounds.width let targetLabelWidth = screenWidth * 0.75 cell.chatNote.numberOfLines = 0 cell.chatNote.preferredMaxLayoutWidth = targetLabelWidth guard let picUrl = URL(string: "https://fastly.picsum.photos/id/0/5000/3333.jpg?hmac=_j6ghY5fCfSD6tvtcV74zXivkJSPIfR9B8w34XeQmvU") else { return cell } // 取消cell之前的同URL网络请求,避免复用错误 URLSession.shared.getAllTasks { tasks in tasks.filter { $0.currentRequest?.url == picUrl }.forEach { $0.cancel() } } URLSession.shared.dataTask(with: picUrl) { [weak cell] data, response, error in guard let data = data, let image = UIImage(data: data), error == nil else { print(error ?? "图片加载失败") return } DispatchQueue.main.async { guard let cell = cell else { return } let screenHeight = UIScreen.main.bounds.height let desiredWidth = targetLabelWidth let desiredHeight = screenHeight * 0.5 let imageAspectRatio = image.size.width / image.size.height // 计算适配后的图片尺寸,保持原比例 var finalWidth: CGFloat = desiredWidth var finalHeight: CGFloat = desiredHeight if desiredWidth / desiredHeight > imageAspectRatio { finalHeight = desiredWidth / imageAspectRatio } else { finalWidth = desiredHeight * imageAspectRatio } let imageAttachment = NSTextAttachment() imageAttachment.image = image // 调整垂直偏移,让图片在label内垂直居中 imageAttachment.bounds = CGRect( x: 0, y: (cell.chatNote.font.descender - finalHeight) / 2, width: finalWidth, height: finalHeight ) let attributedString = NSAttributedString(attachment: imageAttachment) cell.chatNote.attributedText = attributedString // 刷新tableView布局,确保行高更新 tableView.beginUpdates() tableView.endUpdates() } }.resume() return cell }
额外注意事项
- 自定义cell中,确保
chatNote的约束正确:左右边距绑定到contentView(或设置固定宽度等于targetLabelWidth),上下边距绑定到contentView,让label能根据内容自动拉伸高度。 - 若图片尺寸过大,建议先压缩图片再设置到NSTextAttachment,避免内存占用过高。
内容的提问来源于stack exchange,提问作者Jack tileman
相关产品推荐
相关产品推荐

