CATiledLayer 显示以前的图块

CATiledLayer shows previous tiles

使 CATiledLayer 支持的视图无效时,先前的图块仍然 "stuck" 并且未正确无效。

这似乎发生在视图无效时(在主线程上),而与此同时,图块渲染线程仍在处理以前版本的图块。不是缓存新版本的磁贴,而是缓存以前的版本。

CATiledLayer 支持的视图是 UIScrollView 的子视图并且是可缩放的。瓦片的渲染可能很昂贵,可以使用渲染线程 10 毫秒。

例子

演示此问题的示例代码:https://github.com/Q42/CATiledLayerBug

  1. 在 CATiledLayer 中,开始渲染所有红色方块(这大约需要 3 秒才能完成)
  2. 每个渲染步骤大约需要 10 毫秒
  3. 渲染期间(800 毫秒后),使完整视图无效:tiledView.setNeedsDisplay()
  4. 开始渲染所有灰色方块(这又需要大约 3 秒)
  5. 两个瓷砖(随机?)保持红色,而不是变成灰色。

在此处查看 update 函数:https://github.com/Q42/CATiledLayerBug/blob/master/TiledLayerTest/ViewController.swift#L45

解决方法?

这似乎是 CATiledLayer 实现中的错误。由于我无法解决这个问题,有人知道解决此问题的好方法吗?

我已为此提交雷达:http://www.openradar.me/28648050

根据我添加到示例项目的一些进一步日志记录,我认为问题是这样的:

CATiledLayer 有两个绘制线程的渲染线程。如果在 draw(_: CGRect) 调用的执行期间调用了 setNeedsDisplay,则 draw 调用的当前执行已完成并且结果被缓存。缓存值基于之前的 "data source"(本例中只是磁贴颜色),而不是更新后的数据源。

Apple 支持工程师为我提供了解决方法:

  • 向 TiledView 添加一个 updateID 字段
  • 添加 draw(_: CGRect) 调用的开始保存当前 updateID
  • 当"data source"改变时,改变updateID
  • 添加draw(_: CGRect)调用的结尾,将保存的updateID与当前的进行比较。
  • 如果 ID 不同,请安排新的 setNeedsDisplay 通话。

摘录:

override func draw(_ rect: CGRect) {
  let originalID = updateID

  // all actual (slow) drawing code here...

  if originalID != updateID {

    // dispatch a redraw request, but wait a little while first
    DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(17)) {
      self.layer.setNeedsDisplayIn(rect)
    }
  }
}