简体   繁体   English

将 SwiftUI 视图渲染为 UIImage

[英]Render SwiftUI View as an UIImage

I'm trying to render the SwiftUI View as an UIImage then let the user choose to save to the camera roll or send to others via email.我正在尝试将 SwiftUI 视图呈现为 UIImage,然后让用户选择保存到相机胶卷或通过电子邮件发送给其他人。

As an example, I want to render a list of 50 rows into a UIImage.例如,我想将 50 行的列表呈现到 UIImage 中。

struct MyList: View {
    var body: some View {
        List {
            ForEach(0 ..< 50, id:\.self) {
                Text("row \($0)")
            }
        }
    }
}

Searched over the internet for the past few weeks still with no luck.过去几周在互联网上搜索仍然没有运气。 I have tried 2 different approaches.我尝试了 2 种不同的方法。

1. UIHostingController ( source here ) 1. UIHostingController来源在这里

let hosting = UIHostingController(rootView: Text("TEST"))
hosting.view.frame = // Calculate the content size here //
let snapshot = hosting.view.snapshot        // output: an empty snapshot of the size
print(hosting.view.subviews.count)          // output: 0

I've tried layoutSubviews() , setNeedsLayout() , layoutIfNeeded() , loadView() , but all still resulted 0 subviews.我试过layoutSubviews()setNeedsLayout()layoutIfNeeded()loadView() ,但所有结果仍然是 0 个子视图。

2. UIWindow.rootViewController ( source here ) 2. UIWindow.rootViewController来源在这里

var vc = UIApplication.shared.windows[0].rootViewController
vc = vc.visibleController          // loop through the view controller stack to find the top most view controller
let snapshot = vc.view.snapshot    // output: a snapshot of the top most view controller

This almost yields the output I have wanted.这几乎产生了我想要的输出。 However, the snapshot I get is literally a screenshot, ie with a Navigation bar, Tab bar and fixed size (same as screen size).然而,我得到的快照实际上是一个屏幕截图,即带有导航栏、标签栏和固定大小(与屏幕大小相同)。 What I need to capture is simply the view's content without those bars, and may sometimes be larger than the screen (my view is a long list in this example).我需要捕获的只是没有这些条的视图内容,有时可能比屏幕大(我的视图在这个例子中是一个很长的列表)。

Tried to query vc.view.subviews to look for the table view I want, but returning an unhelpful [<_TtGC7SwiftUI16PlatformViewHostGVS_42PlatformViewControllerRepresentableAdaptorGVS_16BridgedSplitViewVVS_22_VariadicView_Children7ElementGVS_5GroupGVS_19_ConditionalContentS4_GVS_17_UnaryViewAdaptorVS_9EmptyView______: 0x7fb061cc4770; frame = (0 0; 414 842); anchorPoint = (0, 0); tintColor = UIExtendedSRGBColorSpace 0 0.478431 1 1; layer = <CALayer: 0x600002d8caa0>>]试图查询vc.view.subviews以查找我想要的表视图,但返回了一个无用的[<_TtGC7SwiftUI16PlatformViewHostGVS_42PlatformViewControllerRepresentableAdaptorGVS_16BridgedSplitViewVVS_22_VariadicView_Children7ElementGVS_5GroupGVS_19_ConditionalContentS4_GVS_17_UnaryViewAdaptorVS_9EmptyView______: 0x7fb061cc4770; frame = (0 0; 414 842); anchorPoint = (0, 0); tintColor = UIExtendedSRGBColorSpace 0 0.478431 1 1; layer = <CALayer: 0x600002d8caa0>>] [<_TtGC7SwiftUI16PlatformViewHostGVS_42PlatformViewControllerRepresentableAdaptorGVS_16BridgedSplitViewVVS_22_VariadicView_Children7ElementGVS_5GroupGVS_19_ConditionalContentS4_GVS_17_UnaryViewAdaptorVS_9EmptyView______: 0x7fb061cc4770; frame = (0 0; 414 842); anchorPoint = (0, 0); tintColor = UIExtendedSRGBColorSpace 0 0.478431 1 1; layer = <CALayer: 0x600002d8caa0>>] [<_TtGC7SwiftUI16PlatformViewHostGVS_42PlatformViewControllerRepresentableAdaptorGVS_16BridgedSplitViewVVS_22_VariadicView_Children7ElementGVS_5GroupGVS_19_ConditionalContentS4_GVS_17_UnaryViewAdaptorVS_9EmptyView______: 0x7fb061cc4770; frame = (0 0; 414 842); anchorPoint = (0, 0); tintColor = UIExtendedSRGBColorSpace 0 0.478431 1 1; layer = <CALayer: 0x600002d8caa0>>] . [<_TtGC7SwiftUI16PlatformViewHostGVS_42PlatformViewControllerRepresentableAdaptorGVS_16BridgedSplitViewVVS_22_VariadicView_Children7ElementGVS_5GroupGVS_19_ConditionalContentS4_GVS_17_UnaryViewAdaptorVS_9EmptyView______: 0x7fb061cc4770; frame = (0 0; 414 842); anchorPoint = (0, 0); tintColor = UIExtendedSRGBColorSpace 0 0.478431 1 1; layer = <CALayer: 0x600002d8caa0>>]

Any help is much appreciated.任何帮助深表感谢。

Would something like this work for you?这样的事情对你有用吗?

import SwiftUI

extension UIView {
    func takeScreenshot() -> UIImage {
        // Begin context
        UIGraphicsBeginImageContextWithOptions(self.bounds.size, false, UIScreen.main.scale)
        // Draw view in that context
        drawHierarchy(in: self.bounds, afterScreenUpdates: true)
        // And finally, get image
        let image = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()

        if (image != nil) {
            UIImageWriteToSavedPhotosAlbum(image!, nil, nil, nil);
            return image!
        }

        return UIImage()
    }
}

struct ContentView: UIViewRepresentable {
    func makeUIView(context: Context) -> UIView {
        let someView = UIView(frame: UIScreen.main.bounds)
        _ = someView.takeScreenshot()
        return someView
    }

    func updateUIView(_ view: UIView, context: Context) {

    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

I am wondering why people say your question is not clear.我想知道为什么人们说你的问题不清楚。 It actually makes perfect sense and it's one of the things I've been looking to do myself.它实际上是完全合理的,这是我一直在寻找自己做的事情之一。

I finally found a way to do things the same way you wish them to be done, it's convoluted but it does work.我终于找到了一种按照您希望完成的方式做事的方法,这很复杂,但确实有效。

The idea is to abuse your second possibility ( UIWindow.rootViewController ).这个想法是滥用你的第二种可能性( UIWindow.rootViewController )。 That one is fun because you take your window, you figure out what you wish to draw, and you draw it.那个很有趣,因为你拿起窗户,弄清楚你想画什么,然后画出来。

But the problem is this is your main window, and you ask to draw your main window.但问题是这是您的主窗口,您要求绘制主窗口。

The way I finally got it working is by doing a UIViewControllerRepresentable , which gives me a UIViewController in my SwiftUI application.我最终让它工作的方法是做一个UIViewControllerRepresentable ,它在我的 SwiftUI 应用程序中为我提供了一个UIViewController Then, inside it, I put a single UIHostingController , which gives me a SwiftUI view inside the UIView.然后,在它里面,我放了一个UIHostingController ,它在 UIView 中给了我一个 SwiftUI 视图。 In other words, I do a bridge.换句话说,我做了一座桥。

In the update function, I can then call a dispatch async to view.layer.render(context) to draw the image.update函数中,我可以调用异步调度到view.layer.render(context)来绘制图像。

Of interest, the UIViewControllerRepresentable will actually be full screen.有趣的是, UIViewControllerRepresentable实际上是全屏的。 So I recommend doing a .scale(...) so your entire list fits in the screen (the render command will not mind it being smaller) and it will be centered.所以我建议做一个.scale(...)以便你的整个列表适合屏幕(渲染命令不会介意它变小)并且它会居中。 So if you know the size of your image in advance, you can do a HStack { VStack { YourStuff Spacer() } Spacer() } so it's rendered top-left.因此,如果您事先知道图像的大小,则可以执行HStack { VStack { YourStuff Spacer() } Spacer() }使其呈现在左上角。

Good luck!祝你好运!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM