Reputation: 314
We got a project from another team as the original dev left the org. I need to fix a defect. I have some code like below where UIGraphicsBeginPDFContextToData
happens on Global
queue. But this gives memory warnings as below UIView
extension's renderToImage
does not happens on main
thread.
I wanted to wrap the renderToImage
in DispatchQueue.main.async
but it would break the for
loop in someFunction
. I can't use .sync
on main thread. Can someone tell me how can I finish renderToImage
waiting on Global
queue?
ORIGINAL CODE:
DispatchQueue.global(qos: .userInitiated).async {
let pdfData = NSMutableData()
UIGraphicsBeginPDFContextToData(pdfData, CGRect.zero, nil)
zip(self.pdfPages, self.pageViews).forEach { (page, view) in
let image = view.renderToImage()
guard let imageData = image.jpegData(compressionQuality: 0.5),
let compressedImage = UIImage(data: imageData) else { return }
UIGraphicsBeginPDFPageWithInfo(CGRect(origin: CGPoint.zero, size: page.originSize), nil)
compressedImage.draw(in: CGRect(origin: CGPoint.zero, size: page.originSize))
DispatchQueue.main.async {
self.progressLabel.text = "Processing PDF... \(page.pageNumber) / \(pageNumbers)"
}
}
UIGraphicsEndPDFContext()
DispatchQueue.main.async {
self.progressIndicator.stopAnimating()
self.progressView.isHidden = true
completionBlock?(pdfData as Data)
}
}
extension UIView {
func renderToImage() -> UIImage {
UIGraphicsBeginImageContextWithOptions(bounds.size, isOpaque, 0.0)
guard let context = UIGraphicsGetCurrentContext() else { return UIImage() }
layer.render(in: context)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image!
}
}
After applying @paulw11 answer:
fileprivate func generatePDF(_ completionBlock: ((_ pdfData: Data) -> Void)?) {
let pageNumbers = pageViews.count
progressView.isHidden = false
progressIndicator.startAnimating()
progressLabel.text = "Processing PDF... \(0) / \(pageNumbers)"
let semaphore = DispatchSemaphore(value: 0)
let pdfContextQueue = DispatchQueue(label: kMessagesFaxPDFConversionQueue, target: DispatchQueue.global(qos: .userInitiated))
let pdfData = NSMutableData()
UIGraphicsBeginPDFContextToData(pdfData, CGRect.zero, nil)
pdfContextQueue.async {
zip(self.pdfPages, self.pageViews).forEach { (page, view) in
view.renderToImage { image in
guard let imageData = image.jpegData(compressionQuality: 0.5),
let compressedImage = UIImage(data: imageData) else { return }
UIGraphicsBeginPDFPageWithInfo(CGRect(origin: CGPoint.zero, size: page.originSize), nil)
compressedImage.draw(in: CGRect(origin: CGPoint.zero, size: page.originSize))
DispatchQueue.main.async {
self.progressLabel.text = "Processing PDF... \(page.pageNumber) / \(pageNumbers)"
}
semaphore.signal()
}
semaphore.wait()
}
UIGraphicsEndPDFContext()
DispatchQueue.main.async {
self.progressIndicator.stopAnimating()
self.progressView.isHidden = true
completionBlock?(pdfData as Data)
}
}
}
extension UIView {
func renderToImage(completion: @escaping ((UIImage) -> Void)) {
DispatchQueue.main.async {
UIGraphicsBeginImageContextWithOptions(self.bounds.size, self.isOpaque, 0.0)
guard let context = UIGraphicsGetCurrentContext() else {
completion(UIImage()); return
}
self.layer.render(in: context)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
completion(image!)
}
}
}
Upvotes: 0
Views: 815
Reputation: 114836
The first thing I would check is if you can't simply perform the work on the main queue - Does it really take that long that there is a noticeable impact on the UI? What else would the user be doing while the PDF was being generated anyway? If they are waiting for the PDF to be generated so that it can be shared or printed, then simply showing a spinner may be enough.
If you do want to keep the work on another queue then I would convert renderToImage
to an asynchronous function and use a DispatchSemaphore
to block your work queue until the loop iteration is complete.
func someFunction() {
let semaphore = DispatchSemaphore(value:0)
let queue = DispatchQueue(label:"PDFQueue", target: DispatchQueue.global(qos: .userInitiated))
queue.async {
for view in PDFViews {
UIGraphicsBeginPDFContextToData(pdfData, CGRect.zero, nil)
zip(self.pdfPages, self.pageViews).forEach { (page, view) in
view.renderToImage { image in
if let image = image {
...
...
}
semaphore.signal()
}
semaphore.wait()
}
}
}
}
extension UIView {
func renderToImage(completion: ((UIImage?)->Void)) {
DispatchQueue.main.async {
UIGraphicsBeginImageContextWithOptions(bounds.size, isOpaque, 0.0)
guard let context = UIGraphicsGetCurrentContext() else {
completion(nil)
}
layer.render(in: context)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
completion(image)
}
}
}
Upvotes: 1