解决方案
这是一个可能的解决方案,它使用插入到rootViewController 的背景中的UIHostingController:
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)
}
}
}