类似于this thread
我想将SwiftUI View
而不是UIView
转换为图像。
答案 0 :(得分:5)
在kontiki answer之后,这是“偏好设置”方式
groupby
答案 1 :(得分:3)
尽管SwiftUI不提供将视图转换为图像的直接方法,但您仍然可以做到。这有点hack,但是效果很好。
在下面的示例中,每当点击两个VStack时,代码就会捕获它们的图像。它们的内容将转换为UIImage(以后可以根据需要将其保存到文件中)。在这种情况下,我只在下面显示它。
请注意,可以改进代码,但是它提供了入门的基础。我使用GeometryReader来获取要捕获的VStack的坐标,但是可以使用Preferences进行改进以使其更健壮。如果需要了解更多信息,请查看提供的链接。
此外,为了将屏幕的区域转换为图像,我们确实需要UIView。该代码使用UIApplication.shared.windows[0].rootViewController.view
来获取顶视图,但是根据您的情况,您可能需要从其他地方获取它。
祝你好运!
这是代码(在iPhone Xr模拟器Xcode 11 beta 4上测试):
import SwiftUI
extension UIView {
func asImage(rect: CGRect) -> UIImage {
let renderer = UIGraphicsImageRenderer(bounds: rect)
return renderer.image { rendererContext in
layer.render(in: rendererContext.cgContext)
}
}
}
struct ContentView: View {
@State private var rect1: CGRect = .zero
@State private var rect2: CGRect = .zero
@State private var uiimage: UIImage? = nil
var body: some View {
VStack {
HStack {
VStack {
Text("LEFT")
Text("VIEW")
}
.padding(20)
.background(Color.green)
.border(Color.blue, width: 5)
.background(RectGetter(rect: $rect1))
.tapAction { self.uiimage = UIApplication.shared.windows[0].rootViewController?.view.asImage(rect: self.rect1) }
VStack {
Text("RIGHT")
Text("VIEW")
}
.padding(40)
.background(Color.yellow)
.border(Color.green, width: 5)
.background(RectGetter(rect: $rect2))
.tapAction { self.uiimage = UIApplication.shared.windows[0].rootViewController?.view.asImage(rect: self.rect2) }
}
if uiimage != nil {
VStack {
Text("Captured Image")
Image(uiImage: self.uiimage!).padding(20).border(Color.black)
}.padding(20)
}
}
}
}
struct RectGetter: View {
@Binding var rect: CGRect
var body: some View {
GeometryReader { proxy in
self.createView(proxy: proxy)
}
}
func createView(proxy: GeometryProxy) -> some View {
DispatchQueue.main.async {
self.rect = proxy.frame(in: .global)
}
return Rectangle().fill(Color.clear)
}
}
答案 2 :(得分:2)
当您可以将不在屏幕上的SwiftUI视图保存到UIImage时,我想出了一个解决方案。 该解决方案看起来有些怪异,但效果很好。
首先创建一个类,用作UIHostingController和SwiftUI之间的连接。在此类中,定义一个函数,您可以调用该函数来复制“视图的”图像。完成此操作后,只需“发布”新值即可更新视图。
class Controller:ObservableObject {
@Published var update=false
var img:UIImage?
var hostingController:MySwiftUIViewHostingController?
init() {
}
func copyImage() {
img=hostingController?.copyImage()
update=true
}
}
然后包装要通过UIHostingController复制的SwiftUI视图
class MySwiftUIViewHostingController: UIHostingController<TestView> {
override func viewDidLoad() {
super.viewDidLoad()
}
func copyImage()->UIImage {
let renderer = UIGraphicsImageRenderer(bounds: self.view.bounds)
return renderer.image(actions: { (c) in
self.view.layer.render(in: c.cgContext)
})
}
}
copyImage()函数将控制器的视图作为UIImage返回
现在您需要显示UIHostingController:
struct MyUIViewController:UIViewControllerRepresentable {
@ObservedObject var cntrl:Controller
func makeUIViewController(context: Context) -> MySwiftUIViewHostingController {
let controller=MySwiftUIViewHostingController(rootView: TestView())
cntrl.hostingController=controller
return controller
}
func updateUIViewController(_ uiViewController: MySwiftUIViewHostingController, context: Context) {
}
}
其余部分如下:
struct TestView:View {
var body: some View {
VStack {
Text("Title")
Image("img2")
.resizable()
.aspectRatio(contentMode: .fill)
Text("foot note")
}
}
}
import SwiftUI
struct ContentView: View {
@ObservedObject var cntrl=Controller()
var body: some View {
ScrollView {
VStack {
HStack {
Image("img1")
.resizable()
.scaledToFit()
.border(Color.black, width: 2.0)
.onTapGesture(count: 2) {
print("tap registered")
self.cntrl.copyImage()
}
Image("img1")
.resizable()
.scaledToFit()
.border(Color.black, width: 2.0)
}
TextView()
ImageCopy(cntrl: cntrl)
.border(Color.red, width: 2.0)
TextView()
TextView()
TextView()
TextView()
TextView()
MyUIViewController(cntrl: cntrl)
.aspectRatio(contentMode: .fit)
}
}
}
}
struct ImageCopy:View {
@ObservedObject var cntrl:Controller
var body: some View {
VStack {
Image(uiImage: cntrl.img ?? UIImage())
.resizable()
.frame(width: 200, height: 200, alignment: .center)
}
}
}
struct TextView:View {
var body: some View {
VStack {
Text("Bla Bla Bla Bla Bla ")
Text("Bla Bla Bla Bla Bla ")
Text("Bla Bla Bla Bla Bla ")
Text("Bla Bla Bla Bla Bla ")
Text("Bla Bla Bla Bla Bla ")
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
您需要img1和img2(被复制的一个)。我将所有内容都放入了一个滚动视图中,以便即使不在屏幕上也能看到图像的复制效果。
答案 3 :(得分:1)
以下是一种可能的解决方案,该解决方案使用插入到UIHostingController
后台的rootViewController
:
func convertViewToData<V>(view: V, size: CGSize, completion: @escaping (Data?) -> Void) where V: View {
guard let rootVC = UIApplication.shared.windows.first?.rootViewController else {
completion(nil)
return
}
let imageVC = UIHostingController(rootView: view.edgesIgnoringSafeArea(.all))
imageVC.view.frame = CGRect(origin: .zero, size: size)
DispatchQueue.main.async {
rootVC.view.insertSubview(imageVC.view, at: 0)
let uiImage = imageVC.view.asImage(size: size)
imageVC.view.removeFromSuperview()
completion(uiImage.pngData())
}
}
您还需要kontiki建议here扩展的asImage
扩展名(设置UIGraphicsImageRendererFormat
是必要的,因为新设备可以具有2x
或3x
规模):
extension UIView {
func asImage(size: CGSize) -> UIImage {
let format = UIGraphicsImageRendererFormat()
format.scale = 1
return UIGraphicsImageRenderer(size: size, format: format).image { context in
layer.render(in: context.cgContext)
}
}
}
假设您具有一些测试视图:
var testView: some View {
ZStack {
Color.blue
Circle()
.fill(Color.red)
}
}
您可以将此视图转换为Data
,该视图可用于返回Image
(或UIImage
):
convertViewToData(view: testView, size: CGSize(width: 300, height: 300)) {
guard let imageData = $0, let uiImage = UIImage(data: imageData) else { return }
return Image(uiImage: uiImage)
}
Data
对象也可以保存到文件中,共享...
struct ContentView: View {
@State var imageData: Data?
var body: some View {
VStack {
testView
.frame(width: 50, height: 50)
if let imageData = imageData, let uiImage = UIImage(data: imageData) {
Image(uiImage: uiImage)
}
}
.onAppear {
convertViewToData(view: testView, size: .init(width: 300, height: 300)) {
imageData = $0
}
}
}
var testView: some View {
ZStack {
Color.blue
Circle()
.fill(Color.red)
}
}
}