UITextView拖拽NSTextAttachment时UITargetedPreview断言报错排查
问题原因分析
这个断言错误的核心是你创建的UITargetedPreview所关联的视图(或其容器视图)不在当前的window层级中。虽然预览能正常显示,但违反了UIKit的API契约——UITargetedPreview要求其关联的view必须处于活跃的window视图树内。
常见触发场景:
- 直接用新创建的离线视图(比如临时实例化的
UIImageView,未添加到任何父视图/window)生成预览 - 获取的附件视图已被从UITextView的视图树中移除
- UITextView本身在拖拽触发时未完全加入window(比如视图控制器还未完成加载)
修复方案
1. 复用UITextView中已有的附件视图
优先获取UITextView里实际显示的附件视图,它本身属于当前视图层级,window属性不为空,能满足API要求。
示例修正代码:
extension CustomTextView: UITextDragDelegate { func textDragView(_ textDragView: UITextDragView, previewForLifting item: UIDragItem, session: UIDragSession) -> UITargetedPreview? { guard let attachment = item.localObject as? NSTextAttachment else { return nil } // 获取附件在文本中的范围 guard let range = self.rangeOfAttachment(attachment) else { return defaultPreview(for: attachment) } // 获取UITextView中渲染该附件的实际视图 if let attachmentView = self.textViewAttachmentView(at: range), attachmentView.window != nil { return UITargetedPreview(view: attachmentView) } // fallback:无法获取实际视图时的处理 return defaultPreview(for: attachment) } // 辅助方法:获取附件的文本范围 private func rangeOfAttachment(_ attachment: NSTextAttachment) -> NSRange? { let attributedText = self.attributedText var range: NSRange? attributedText.enumerateAttribute(.attachment, in: NSRange(location: 0, length: attributedText.length)) { value, attrRange, _ in if let attrAttachment = value as? NSTextAttachment, attrAttachment == attachment { range = attrRange } } return range } // 辅助方法:获取附件对应的显示视图 private func textViewAttachmentView(at range: NSRange) -> UIView? { let glyphRange = self.layoutManager.glyphRange(forCharacterRange: range, actualCharacterRange: nil) return self.layoutManager.textAttachmentView(forGlyphAt: glyphRange.location) } // fallback:创建临时预览视图(需加入window) private func defaultPreview(for attachment: NSTextAttachment) -> UITargetedPreview? { guard let image = attachment.image else { return nil } let tempView = UIImageView(image: image) // 将临时视图添加到当前window,确保处于层级内 guard let window = self.window else { return nil } tempView.frame = self.convert(self.bounds, to: window) window.addSubview(tempView) // 创建预览 let preview = UITargetedPreview(view: tempView) // 拖拽开始后立即移除临时视图,避免残留 DispatchQueue.main.async { tempView.removeFromSuperview() } return preview } }
2. 检查其他拖拽相关方法
如果你实现了textDragView:dragPreviewForItem:session:,也要确保同样遵循上述规则——不要用离线视图生成预览。
3. 确认UITextView的window状态
拖拽触发时,确保你的CustomTextView已经完全加载并加入window(比如在viewDidAppear之后再允许拖拽,避免在视图控制器初始化阶段触发拖拽)。
内容的提问来源于stack exchange,提问作者Craig Swanson
相关产品推荐
相关产品推荐

