如何将不在根层次结构中的 SwiftUI 视图呈现为 UIImage?
Posted
技术标签:
【中文标题】如何将不在根层次结构中的 SwiftUI 视图呈现为 UIImage?【英文标题】:How do I render a SwiftUI View that is not at the root hierarchy as a UIImage? 【发布时间】:2019-12-13 22:58:39 【问题描述】:假设我有一个不是 ContentView 的简单 SwiftUI 视图,例如:
struct Test: View
var body: some View
VStack
Text("Test 1")
Text("Test 2")
如何将此视图呈现为 UIImage?
我已经研究过以下解决方案:
extension UIView
func asImage() -> UIImage
let renderer = UIGraphicsImageRenderer(bounds: bounds)
return renderer.image rendererContext in
layer.render(in: rendererContext.cgContext)
但似乎这样的解决方案只适用于 UIView,而不适用于 SwiftUI 视图。
【问题讨论】:
ericasadun.com/2019/06/20/… 【参考方案1】:这是适合我的方法,因为我需要让图像与其他图像并排放置时的大小完全相同。希望对其他人有所帮助。
演示:上面的分隔线是 SwiftUI 渲染的,下面是图片(在边框中显示大小)
extension View
func asImage() -> UIImage
let controller = UIHostingController(rootView: self)
// locate far out of screen
controller.view.frame = CGRect(x: 0, y: CGFloat(Int.max), width: 1, height: 1)
UIApplication.shared.windows.first!.rootViewController?.view.addSubview(controller.view)
let size = controller.sizeThatFits(in: UIScreen.main.bounds.size)
controller.view.bounds = CGRect(origin: .zero, size: size)
controller.view.sizeToFit()
let image = controller.view.asImage()
controller.view.removeFromSuperview()
return image
extension UIView
func asImage() -> UIImage
let renderer = UIGraphicsImageRenderer(bounds: bounds)
return renderer.image rendererContext in
// [!!] Uncomment to clip resulting image
// rendererContext.cgContext.addPath(
// UIBezierPath(roundedRect: bounds, cornerRadius: 20).cgPath)
// rendererContext.cgContext.clip()
// As commented by @MaxIsom below in some cases might be needed
// to make this asynchronously, so uncomment below DispatchQueue
// if you'd same met crash
// DispatchQueue.main.async
layer.render(in: rendererContext.cgContext)
//
// TESTING
struct TestableView: View
var body: some View
VStack
Text("Test 1")
Text("Test 2")
struct TestBackgroundRendering: View
var body: some View
VStack
TestableView()
Divider()
Image(uiImage: render())
.border(Color.black)
private func render() -> UIImage
TestableView().asImage()
struct TestBackgroundRendering_Previews: PreviewProvider
static var previews: some View
TestBackgroundRendering()
【讨论】:
有效!现在,如果我需要专门在我的视图中使用 UIImage 类型而不是 Image 那可能吗?由于在此处的代码中我们有Image(uiImage: render())
,因此是否存在与渲染等效的 UIImage 表达式?我们甚至可以在 SwiftUI 视图中使用 UIImage 吗?
@Asperi 显然,如果TestableView
包含使用@Binding
传递给它的动态数据,则它不起作用。在 UIView 扩展的layer.render
行中获取Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)
。
我在动态数据方面遇到了同样的问题。 @AnaghSharma 你找到解决方法了吗?
我可以通过用self.layer.render(in: rendererContext.cgContext)
包裹DispatchQueue.main.async
来防止它崩溃
我使用 DispatchQueue.main.async [weak self] in layer.render 得到空图像,但没有正确图像【参考方案2】:
Asperi 的解决方案有效,但是如果您需要没有白色背景的图像,则必须添加以下行:
controller.view.backgroundColor = .clear
您的视图扩展将是:
extension View
func asImage() -> UIImage
let controller = UIHostingController(rootView: self)
// locate far out of screen
controller.view.frame = CGRect(x: 0, y: CGFloat(Int.max), width: 1, height: 1)
UIApplication.shared.windows.first!.rootViewController?.view.addSubview(controller.view)
let size = controller.sizeThatFits(in: UIScreen.main.bounds.size)
controller.view.bounds = CGRect(origin: .zero, size: size)
controller.view.sizeToFit()
controller.view.backgroundColor = .clear
let image = controller.view.asImage()
controller.view.removeFromSuperview()
return image
【讨论】:
如果将修饰符作为 .backgroundColor() 添加到 TestableView() 将呈现该颜色。以上是关于如何将不在根层次结构中的 SwiftUI 视图呈现为 UIImage?的主要内容,如果未能解决你的问题,请参考以下文章
尝试呈现其视图不在 Windows 层次结构中的 ViewController
尝试在其视图不在窗口层次结构中的 UIViewController 上呈现 UIViewController
尝试在其视图不在窗口层次结构中的 UIViewController 上呈现 UIViewController