如何解决UITableViewCell内UITextView截图失败的问题?
UITableViewCell内UITextView截图失败的解决方案
问题场景
尝试对UITableViewCell内的UITextView进行截图,使用的单元格代码:
class MyTableViewCell: UITableViewCell { @IBOutlet weak var myTextView: UITextView! func bind(){ let renderer = UIGraphicsImageRenderer(size: self.myTextView.bounds.size) let myImage = renderer.image { (context) in var bounds = self.myTextView.bounds bounds.origin = CGPoint.zero self.myTextView.drawHierarchy(in: bounds, afterScreenUpdates: true) } // 后续处理myImage } }
bind()方法在tableView:cellForRowAt:中调用:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{ let myCell = tableView.dequeueReusableCell(withIdentifier: "MyTableViewCell", for: indexPath ) as! MyTableViewCell myCell.bind() return myCell }
遇到的错误
- 设置
afterScreenUpdates: true时,抛出错误且myImage为nil:
[Snapshotting] View (UITextView) drawing with afterScreenUpdates:YES inside CoreAnimation commit is not supported.
- 设置
afterScreenUpdates: false时,同样抛出错误且myImage为nil:
[Snapshotting] Rendering a view (UITextView) that has not been rendered at least once requires afterScreenUpdates:YES.
- 尝试使用
layer.render方式截图,myImage仍为nil:
let rect = self.myTextView.bounds UIGraphicsBeginImageContextWithOptions(rect.size, false, 0.0) let context: CGContext = UIGraphicsGetCurrentContext()! self.myTextView.layer.render(in: context) let myImage : UIImage = UIGraphicsGetImageFromCurrentImageContext()! UIGraphicsEndImageContext()
解决方案
1. 延迟到视图完成渲染后再截图
在cellForRowAt:阶段,单元格还未完成CoreAnimation的渲染提交,此时直接调用截图会触发冲突。可以将截图逻辑放到主队列的异步任务中,等待当前渲染周期完成后执行:
func bind(){ DispatchQueue.main.async { let renderer = UIGraphicsImageRenderer(size: self.myTextView.bounds.size) let myImage = renderer.image { (context) in self.myTextView.drawHierarchy(in: self.myTextView.bounds, afterScreenUpdates: false) } // 在这里处理生成的myImage } }
2. 切换到willDisplayCell代理方法中执行截图
UITableView的willDisplay:forRowAt:方法会在单元格即将显示到屏幕上时调用,此时视图已经完成布局和初始渲染,适合执行截图操作:
// 替换cellForRowAt中的bind调用,改为在willDisplay中执行 func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{ let myCell = tableView.dequeueReusableCell(withIdentifier: "MyTableViewCell", for: indexPath ) as! MyTableViewCell return myCell } func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { guard let myCell = cell as? MyTableViewCell else { return } myCell.bind() }
3. 强制视图完成布局后再截图
如果需要在bind方法内立即尝试截图,可以先强制TextView完成布局,再结合异步任务执行:
func bind(){ // 强制TextView完成当前布局 myTextView.layoutIfNeeded() // 等待布局完成后执行截图 DispatchQueue.main.async { let renderer = UIGraphicsImageRenderer(size: self.myTextView.bounds.size) let myImage = renderer.image { (context) in self.myTextView.drawHierarchy(in: self.myTextView.bounds, afterScreenUpdates: false) } // 处理生成的图片 } }
错误原因说明
cellForRowAt:方法属于CoreAnimation的提交阶段,此时视图还未完成渲染,调用drawHierarchy(with:afterScreenUpdates:)会触发系统的快照冲突。layer.render(in:)方式失效,是因为UITextView的文字内容属于UIKit层面的绘制逻辑,图层渲染时可能还未完成文字的绘制,导致截图为空。
内容的提问来源于stack exchange,提问作者supermonkey
相关产品推荐
相关产品推荐

