我该如何将SwiftUI中的Image
转换为UIImage
?
let image = Image(systemName: "circle.fill")
let UIImage = image as UIImage
没有直接将Image转换为UIImage的方法。相反,您应该将Image视为View,并尝试将该View转换为UIImage。 Image符合View,因此我们已经拥有所需的View。现在我们只需要将该View转换为UIImage。
我们需要两个组件来实现这一点。首先,一个将我们的Image/View更改为UIView的函数,其次是将我们创建的UIView更改为UIImage的函数。
为了更方便,这两个函数都声明为它们相应类型的扩展。
extension View {
// This function changes our View to UIView, then calls another function
// to convert the newly-made UIView to a UIImage.
public func asUIImage() -> UIImage {
let controller = UIHostingController(rootView: self)
// Set the background to be transparent incase the image is a PNG, WebP or (Static) GIF
controller.view.backgroundColor = .clear
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()
// here is the call to the function that converts UIView to UIImage: `.asUIImage()`
let image = controller.view.asUIImage()
controller.view.removeFromSuperview()
return image
}
}
extension UIView {
// This is the function to convert UIView to UIImage
public func asUIImage() -> UIImage {
let renderer = UIGraphicsImageRenderer(bounds: bounds)
return renderer.image { rendererContext in
layer.render(in: rendererContext.cgContext)
}
}
}
如何使用?
let image: Image = Image("MyImageName") // Create an Image anyhow you want
let uiImage: UIImage = image.asUIImage() // Works Perfectly
奖励
正如我所说,我们将图像视为视图。在此过程中,我们不使用任何特定的图像功能,唯一重要的是我们的图像是一个视图(符合视图协议)。 这意味着使用此方法,您不仅可以将图像转换为UIImage,还可以将任何视图转换为UIImage。
var myView: some View {
// create the view here
}
let uiImage = myView.asUIImage() // Works Perfectly
UIApplication.shared.windows.first!.rootViewController?.view.addSubview(controller.view)
?我收到了弃用警告:“'windows' was deprecated in iOS 15.0: Use UIWindowScene.windows on a relevant window scene instead"
。 - Rillieux这种事在SwiftUI中是不可能的,我敢打赌它将永远不会实现。这与整个框架概念相违背。然而,你可以朝相反的方向发展:
let uiImage = UIImage(systemName: "circle.fill")
let image = Image(uiImage: uiImage)
extension Image {
@MainActor
func getUIImage(newSize: CGSize) -> UIImage? {
let image = resizable()
.scaledToFill()
.frame(width: newSize.width, height: newSize.height)
.clipped()
return ImageRenderer(content: image).uiImage
}
}
let image = Image("someFromAssets")
let size = CGSize(width: 100, height: 100)
let uiImage = image.getUIImage(newSize: size)
public func convert(image: Image, callback: @escaping ((UIImage?) -> Void)) {
DispatchQueue.main.async {
let renderer = ImageRenderer(content: image)
// to adjust the size, you can use this (or set a frame to get precise output size)
// renderer.scale = 0.25
// for CGImage use renderer.cgImage
callback(renderer.uiImage)
}
}
extension View {
/// Usually you would pass `@Environment(\.displayScale) var displayScale`
@MainActor func render(scale displayScale: CGFloat = 1.0) -> UIImage? {
let renderer = ImageRenderer(content: self)
renderer.scale = displayScale
return renderer.uiImage
}
}
extension View {
func snapshot() -> UIImage {
let controller = UIHostingController(rootView: self)
let view = controller.view
let targetSize = controller.view.intrinsicContentSize
view?.bounds = CGRect(origin: .zero, size: targetSize)
view?.backgroundColor = .clear
let renderer = UIGraphicsImageRenderer(size: targetSize)
return renderer.image { _ in
view?.drawHierarchy(in: controller.view.bounds, afterScreenUpdates: true)
}
}
}
struct RenderView: View {
let text: String
var body: some View {
Text(text)
.font(.largeTitle)
.foregroundStyle(.white)
.padding()
.background(.blue)
.clipShape(Capsule())
}
}
struct ContentView: View {
@State private var text = "Your text here"
@State private var uiImage = UIImage(systemName: "photo")
@Environment(\.displayScale) private var displayScale
var body: some View {
let renderedImage = Image(uiImage: uiImage!)
let textField = TextField("Enter some text", text: $text)
.textFieldStyle(.roundedBorder)
.padding()
.background(.blue)
.clipShape(Capsule())
VStack {
renderedImage
ShareLink("Export", item: renderedImage, preview: SharePreview(Text("Shared image"), image: renderedImage))
textField
}
.onChange(of: text) { _ in
uiImage = RenderView(text: text).render(scale: displayScale)!
// Note that the following will not work for some reason
// uiImage = textField.render(scale: displayScale)!
// This kind of works, but rendered size differs from original
// uiImage = textField.snapshot()
}
}
}
我一直在使用一个包装器来实现这个解决方法
struct AppImage: View {
private let name: String
var body: some View {
Image(name)
}
var asUIImage: UIImage? {
UIImage(named: name)
}
}
import UIKit
import Kingfisher
struct AppImage: View {
private let urlString: String
var body: some View {
KFImage(URL(string: urlString))
}
var asUIImage: UIImage {
let imageView = UIImageView()
imageView.kf.setImage(with: URL(string: urlString))
return imageView.image ?? UIImage(named: "placeholder-image")!
}
}
AppImage
类型,则可以在应用程序中使用任何图像作为UIImage
:
AppImage(name: "image-name").asUIImage
或者
AppImage(urlString: "https://url-to-your-image.com").asUIImage
UIImage
是UIKit
的一部分,而不是SwiftUI
。你到底想做什么?没有SwiftUI
,你可以很容易地通过非常详细的文档在UIImage
、CGImage
和CIImage
之间进行转换。如果你认为Image
更像是一个UIImageView
,那么你真正寻找的是什么呢? - user7014451