iOS - 在不冻结 UI 的情况下创建大量 UIImageView

iOS - Create a huge amount of UIImageViews without freezing the UI

我必须渲染创建大量(~10000)UIImage 个对象,这些对象稍后会使用 MapBox 添加到地图中。图像是通过将 UILabel 渲染到 UIImage 来创建的(遗憾的是,标签不能直接在地图上以正确的字体渲染)。

我想,因为在视图呈现在地图上之前我不会将视图添加到层​​次结构中,所以我可以在后台线程上创建视图以避免 UI 冻结。然而,这似乎是不可能的。

我的问题是,有没有办法通过将 UILabel 渲染到图像而不冻结 UI 来创建大量 UIImage 对象?谢谢你的帮助!

我将 UIView 渲染到图像的代码如下:

private func render(_ view: UIView) -> UIImage? {
  UIGraphicsBeginImageContext(view.frame.size)
  guard let currentContext = UIGraphicsGetCurrentContext() else {return nil}
  view.layer.render(in: currentContext)
  return UIGraphicsGetImageFromCurrentImageContext()
}

以及我渲染到图像的标签示例:

bigItemList.forEach { item in
  // work to process the item.. 

  // I have to call the rendering om the main thread, which causes the UI to freeze
  DispatchQueue.main.async {
    addImage(createLabel(text: label))
  }
}

func createLabel(text: String?) -> UIImage? {
  let label = UILabel()
  label.textAlignment = .center
  label.font = .boldSystemFont(ofSize: 14)
  label.textColor = .mainBlue
  label.text = text
  label.sizeToFit()
  label render(depthLabel)
}

您很可能已经在主队列中了。尝试在另一个循环中执行循环:

   DispatchQueue.init(label: "Other queue").async {
       bigItemList.forEach { item in
         // work to process the item.. 

         // I have to call the rendering om the main thread, which causes the UI to freeze
         DispatchQueue.main.async {
           addImage(createLabel(text: label))
         }
       }
   }

编辑:如果它已经在另一个队列中,您还缺少对渲染的 UIGraphicsEndImageContext() 调用:

private func render(_ view: UIView) -> UIImage? {
  UIGraphicsBeginImageContext(view.frame.size)
  guard let currentContext = UIGraphicsGetCurrentContext() else {return nil}
  view.layer.render(in: currentContext)
  let img = UIGraphicsGetImageFromCurrentImageContext()
  UIGraphicsEndImageContext()
  return img
}

编辑:我看到的另一件事是标签分配是循环中最耗时的。尝试分配一次,因为它是可重复使用的:

let label = UILabel()
func createLabel(text: String?) -> UIImage? {
  label.textAlignment = .center
  ...
  ...
}

在任何非主线程上访问 UILabel 都是不安全的。你在这里想要的工具是 CATextLayer ,它会做所有相同的事情(使用稍微笨拙的语法),同时是线程安全的。例如:

func createLabel(text: String) -> UIImage? {
    let label = CATextLayer()
    let uiFont = UIFont.boldSystemFont(ofSize: 14)
    label.font = CGFont(uiFont.fontName as CFString)
    label.fontSize = 14
    label.foregroundColor = UIColor.blue.cgColor
    label.string = text
    label.bounds = CGRect(origin: .zero, size: label.preferredFrameSize())
    let renderer = UIGraphicsImageRenderer(size: label.bounds.size)
    return renderer.image { context in
        label.render(in: context.cgContext)
    }
}

这对 运行 在任何队列上都是安全的。