基于View的NSTableView拖拽时文本标签空白问题及优化方案咨询
你遇到的这个问题我之前也碰到过——NSTableView行拖拽时,默认的NSTextField因为用了NSTextLayer,导致拖拽快照没法正确渲染文本,确实挺头疼的。你重写draw()的方案虽然能解决,但确实有点“hack”感,这里给你两个更优雅的解决思路:
方案一:强制NSTextField使用普通Backing Layer
不用子类化,直接在你创建NSTextField的代码里,强制它使用普通的视图 backing layer 替代NSTextLayer。修改你的tableView(_:viewFor:row:)方法就行:
func tableView(_ tv: NSTableView, viewFor tc: NSTableColumn?, row: Int) -> NSView? { let v = (tv.makeView(withIdentifier: viewID, owner: nil) as? NSTextField) ?? NSTextField() v.isSelectable = false v.isEditable = false v.stringValue = data[row] v.identifier = viewID // 添加这两行代码 v.wantsLayer = true v.layerClass = NSBackingLayer.self return v }
原理很简单:NSTextField默认会用NSTextLayer来优化文本渲染,但这个图层在TableView生成拖拽快照的流程里会被跳过。强制设置layerClass为NSBackingLayer后,它就会用普通的视图 backing layer,就能正常被快照捕获了。
方案二:手动生成拖拽快照
如果你需要更灵活的拖拽外观控制(比如想给拖拽的文本加个阴影、改个颜色),可以直接实现tableView(_:dragImageForRow:withEvent:offset:)代理方法,手动生成拖拽时显示的图像:
func tableView(_ tableView: NSTableView, dragImageForRow row: Int, withEvent event: NSEvent, offset dragImageOffset: UnsafeMutablePointer<NSPoint>) -> NSImage? { // 获取对应行的文本字段视图 guard let textField = tableView.view(atColumn: 0, row: row, makeIfNecessary: true) as? NSTextField else { return nil } // 生成视图的位图快照 guard let bitmapRep = textField.bitmapImageRepForCachingDisplay(in: textField.bounds) else { return nil } textField.cacheDisplay(in: textField.bounds, to: bitmapRep) // 转换成NSImage返回 return NSImage(cgImage: bitmapRep.cgImage!, size: textField.bounds.size) }
这个方法绕开了TableView默认的快照逻辑,直接从视图生成位图,不管原视图用什么图层,都能正确渲染出文本。
为什么重写draw()能解决?
其实你观察到的图层切换是关键:当你重写draw(_:)方法时,AppKit会认为这个视图需要自定义绘制,自动把它的图层从NSTextLayer切换成普通的_NSViewBackingLayer,这样就能被拖拽快照捕获了。但这种方式相当于“骗”AppKit切换图层,不如上面两种方案直接。
内容的提问来源于stack exchange,提问作者hnh

