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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 17:05:30