将 SwiftUI 视图渲染为 UIImage
Render SwiftUI View as an UIImage
我正在尝试将 SwiftUI 视图呈现为 UIImage,然后让用户选择保存到相机胶卷或通过电子邮件发送给其他人。
例如,我想将 50 行的列表渲染到 UIImage 中。
struct MyList: View {
var body: some View {
List {
ForEach(0 ..< 50, id:\.self) {
Text("row \([=10=])")
}
}
}
}
过去几周在互联网上搜索仍然没有成功。我尝试了两种不同的方法。
1。 UIHostingController (source here)
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
我已经尝试了 layoutSubviews()
、setNeedsLayout()
、layoutIfNeeded()
、loadView()
,但结果仍然是 0 个子视图。
2。 UIWindow.rootViewController (source here)
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
这几乎可以产生我想要的输出。然而,我得到的快照实际上是一个屏幕截图,即带有导航栏、选项卡栏和固定大小(与屏幕大小相同)。我需要捕获的只是没有这些栏的视图内容,有时可能比屏幕大(在这个例子中我的视图是一个长列表)。
试图查询 vc.view.subviews
以寻找我想要的 table 视图,但返回了无用的 [<_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>>]
。
非常感谢任何帮助。
这样的东西对你有用吗?
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()
}
}
我想知道为什么人们说你的问题不清楚。这实际上非常有道理,也是我一直想自己做的事情之一。
我终于找到了一种按照您希望的方式做事的方法,它很复杂,但确实有效。
这个想法是滥用你的第二种可能性 (UIWindow.rootViewController
)。那一个很有趣,因为你拿着你的window,你弄清楚你想画什么,然后画它。[=19=]
但问题是这是你的主window,而你要求绘制你的主window。
我最终让它工作的方法是执行 UIViewControllerRepresentable
,它在我的 SwiftUI 应用程序中给了我一个 UIViewController
。然后,在其中,我放了一个 UIHostingController
,它在 UIView 中提供了一个 SwiftUI 视图。也就是说,我做桥。
在 update
函数中,我可以调用异步调度到 view.layer.render(context)
来绘制图像。
有趣的是,UIViewControllerRepresentable
实际上是全屏的。所以我建议做一个 .scale(...)
以便你的整个列表适合屏幕(渲染命令不会介意它变小)并且它会居中。因此,如果您事先知道图像的大小,则可以执行 HStack { VStack { YourStuff Spacer() } Spacer() }
以使其呈现在左上角。
祝你好运!
我正在尝试将 SwiftUI 视图呈现为 UIImage,然后让用户选择保存到相机胶卷或通过电子邮件发送给其他人。
例如,我想将 50 行的列表渲染到 UIImage 中。
struct MyList: View {
var body: some View {
List {
ForEach(0 ..< 50, id:\.self) {
Text("row \([=10=])")
}
}
}
}
过去几周在互联网上搜索仍然没有成功。我尝试了两种不同的方法。
1。 UIHostingController (source here)
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
我已经尝试了 layoutSubviews()
、setNeedsLayout()
、layoutIfNeeded()
、loadView()
,但结果仍然是 0 个子视图。
2。 UIWindow.rootViewController (source here)
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
这几乎可以产生我想要的输出。然而,我得到的快照实际上是一个屏幕截图,即带有导航栏、选项卡栏和固定大小(与屏幕大小相同)。我需要捕获的只是没有这些栏的视图内容,有时可能比屏幕大(在这个例子中我的视图是一个长列表)。
试图查询 vc.view.subviews
以寻找我想要的 table 视图,但返回了无用的 [<_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>>]
。
非常感谢任何帮助。
这样的东西对你有用吗?
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()
}
}
我想知道为什么人们说你的问题不清楚。这实际上非常有道理,也是我一直想自己做的事情之一。
我终于找到了一种按照您希望的方式做事的方法,它很复杂,但确实有效。
这个想法是滥用你的第二种可能性 (UIWindow.rootViewController
)。那一个很有趣,因为你拿着你的window,你弄清楚你想画什么,然后画它。[=19=]
但问题是这是你的主window,而你要求绘制你的主window。
我最终让它工作的方法是执行 UIViewControllerRepresentable
,它在我的 SwiftUI 应用程序中给了我一个 UIViewController
。然后,在其中,我放了一个 UIHostingController
,它在 UIView 中提供了一个 SwiftUI 视图。也就是说,我做桥。
在 update
函数中,我可以调用异步调度到 view.layer.render(context)
来绘制图像。
有趣的是,UIViewControllerRepresentable
实际上是全屏的。所以我建议做一个 .scale(...)
以便你的整个列表适合屏幕(渲染命令不会介意它变小)并且它会居中。因此,如果您事先知道图像的大小,则可以执行 HStack { VStack { YourStuff Spacer() } Spacer() }
以使其呈现在左上角。
祝你好运!