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)
}
}
这对 运行 在任何队列上都是安全的。
我必须渲染创建大量(~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)
}
}
这对 运行 在任何队列上都是安全的。