Reputation: 12615
I'm trying to render a CALayer
from SwiftSVG package (on github) as a UIImage
. It isn't working.
Source .svg file defines a viewBox 1169 x 1000.
If I add CALayer
to UIView
and add UIView
(not UIImage
) to view hierarchy, the SVG graphics are perfect, so CALayer
is fine.
I added renderer.cgContext
based drawing commands in the closure, to draw an outline around the rendering area. It worked. The resulting UIImage
had an outline drawn, proving the image renderer block itself works. Thus...
svgLayer.render()
does not render visible graphics in the produced image.
svgView.drawHierarchy()
doesn't work either (for example if I add the SVG layer as a sublayer of a view and try to render the view instead).
let viewBox : CGRect? = CGRectMake(0, 0, 116.9, 100)
let filename = (fieldKey + ".svg").lowercased()
let svgURL = gbl_svgDataUrl.appendingPathComponent(filename)
let svgLayer = CALayer(SVGURL: svgURL) { (svgLayer) in
svgLayer.fillColor = color.cgColor
if let viewBox {
svgLayer.resizeToFit(viewBox)
}
}
CAN GET DRAWN GRAPHICS INTO IMAGE BUT NOT RENDERED CALAYER OR VIEW:
let format = UIGraphicsImageRendererFormat.preferred()
format.opaque = false
let image = UIGraphicsImageRenderer(size: viewBox.size, format: format).image { renderer in
svgLayer.render(in: renderer.cgContext) // <-- DOESN'T WORK
//svgView.drawHierarchy(in: viewBox, // <-- DOESN'T WORK
// afterScreenUpdates: false)
}
Upvotes: 0
Views: 347
Reputation: 12615
I figured out a resolution. Tricky situation, so I'm answering my own question so this may come up in searches for related terms and help someone in the future
@Sweeper's comment to the question right, when trying to create a CALayer with SwiftSVG CALayer(SVGURL), it is async, and the layer can only be added to the view inside the closure. What confused me is that's different than how SwiftSVG docs (at github project main page) shows the UIView(SVGURL:)
API, which does complete synchronously, and the view is ready when the main call completes. Therefore, I switched from using CALayer(SVGURL:)
to UIView(SVGURL:)
to avoid async problems. And that necessitated to using view.drawHierarchy() instead of layer.render()
Then I hit a Catch-22 with this line:
svgView.drawHierarchy(in: viewBox, afterScreenUpdates: ??)
If I set afterScreenUpdates:false
, I got this warning in the console:
"Rendering a view (0x117405440, UIView) that has not been rendered at least once requires afterScreenUpdates:YES."
afterScreenUpdates:true
, I got this error:"View (0x10a159e00, UIView) drawing with afterScreenUpdates:YES inside CoreAnimation commit is not supported."
And with either error the SVG View would not render into the image.
I tried forcing the view to display with setNeedsLayout() and layoutIfNeeded() before image rendering, which didn't work. Neither did moving code into viewDidLayoutSubviews().
Solution: afterScreenUpdates:true
and add a delay to avoid the 2nd error.
let viewBox = CGRectMake(0, 0, 116.9, 100)
let filename = (fieldKey + ".svg").lowercased()
let svgURL = gbl_svgDataUrl.appendingPathComponent(filename)
let svgView = UIView(SVGURL: svgURL) { (svgLayer) in
svgLayer.fillColor = color.cgColor
svgLayer.resizeToFit(viewBox)
}
let format = UIGraphicsImageRendererFormat.preferred()
format.opaque = false
var image: UIImage?
// Add a slight delay to ensure the view has been rendered at least once
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
image = UIGraphicsImageRenderer(size: viewBox.size, format: format).image { renderer in
svgView.drawHierarchy(in: viewBox, afterScreenUpdates: true)
}
if let image = image {
let imageView = UIImageView(image: image)
imageView.frame = viewBox
imageView.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(imageView)
NSLayoutConstraint.activate([
imageView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor),
imageView.centerYAnchor.constraint(equalTo: self.view.centerYAnchor),
imageView.heightAnchor.constraint(equalToConstant: viewBox.size.height),
imageView.widthAnchor.constraint(equalToConstant: viewBox.size.width),
])
}
}
Upvotes: 0