ImageRenderer
类可以将任何 SwiftUI 视图层次结构渲染为图像,然后可以以其他方式保存、共享或重用该图像。(iOS 16 中的改进功能)
基本例子:
func generateImage(note: Note) -> UIImage? {
// 创建渲染器 & 配置渲染器 & 设置颜色格式以确保正确显示
let renderer = ImageRenderer(content: NoteShareImage1View(note: note))
renderer.scale = UIScreen.main.scale
renderer.colorMode = .nonLinear
// 获取生成图片
if let uiImage = renderer.uiImage {
return uiImage
} else {
return nil
}
}
ImageRenderer
,这意味着需要用 @MainActor
标记渲染代码renderer.scale
,图像将以 1 倍比例渲染,这在 2 倍或 3 倍分辨率的屏幕上看起来会很模糊ImageRenderer(content:)
初始值设定项中,但将它们分离到专用视图中会让代码更清晰UIGraphicsImageRenderer
不同,没有简单的方法可以直接从 ImageRenderer
读取 PNG 或 JPEG 数据;因此正如在代码中看到的,我们需要读取其结果的 UIImage
方法。这使得跨平台用户的代码更加复杂完整例子:使用 @MainActor
确保渲染代码可以安全调用,将视图渲染到它自己的结构中,然后使用 ShareLink
共享结果。
struct ContentView: View {
@State private var text = "Your text here"
@State private var renderedImage = Image(systemName: "photo")
// 获取设备的屏幕显示倍数,后面用于生成
@Environment(\\.displayScale) var displayScale
var body: some View {
VStack {
renderedImage
ShareLink(
"Export",
item: renderedImage,
preview: SharePreview(Text("Shared image"), image: renderedImage)
)
TextField("Enter some text", text: $text)
.textFieldStyle(.roundedBorder)
.padding()
}
// 当显示视图时以及每当 text 发生变化时,都会调用渲染方法
.onChange(of: text) { _ in
render()
}
.onAppear {
render()
}
}
// 执行渲染方法:
@MainActor func render() {
let renderer = ImageRenderer(content: RenderView(text: text))
// 使用设备的显示倍数去渲染生成图片
renderer.scale = displayScale
if let uiImage = renderer.uiImage {
renderedImage = Image(uiImage: uiImage)
}
}
}
<aside> 💡
注意:有时当内容过大时,生成图片会出错。报错信息大概是以下这样:
error 'Texture Descriptor Validation MTLTextureDescriptor has height (9946) greater than the maximum allowed size of 8192.
MTLTexture
),而 Metal 的最大允许纹理高度是 8192,这超出了设备允许的最大纹理你可以采取以下几种方法来解决这个问题:
在生成图片之前,可以先将图片尺寸缩小,确保其尺寸不超过 Metal 的限制。你可以在生成图像之前先调用这个函数来确保它不会超过最大尺寸:
swift
复制编辑
func resizeImage(_ image: UIImage, maxSize: CGFloat) -> UIImage? {
let aspectRatio = image.size.width / image.size.height
var newWidth: CGFloat
var newHeight: CGFloat
if image.size.width > image.size.height {
newWidth = maxSize
newHeight = newWidth / aspectRatio
} else {
newHeight = maxSize
newWidth = newHeight * aspectRatio
}
let newSize = CGSize(width: newWidth, height: newHeight)
UIGraphicsBeginImageContextWithOptions(newSize, false, image.scale)
image.draw(in: CGRect(origin: .zero, size: newSize))
let resizedImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return resizedImage
}
如果图片尺寸过大,并且你不关心完整的图像,你可以裁剪图片的一部分,以适应纹理大小限制。可以通过裁剪图片的一部分来减少尺寸:
在这里,你可以通过指定裁剪的区域来裁剪图片。
func cropImage(_ image: UIImage, rect: CGRect) -> UIImage? {
guard let cgImage = image.cgImage?.cropping(to: rect) else { return nil }
return UIImage(cgImage: cgImage)
}
如果你正在使用较高分辨率的图像(例如原始图像或者很大的图像资源),你可能需要先将图像压缩到适当的大小或分辨率,然后再进行后续操作。可以使用 UIImageJPEGRepresentation
或 UIImagePNGRepresentation
等方法将图像压缩。
if let compressedData = image.jpegData(compressionQuality: 0.5) {
let compressedImage = UIImage(data: compressedData)
// 使用压缩后的图片
}
ImageRenderer
类还可以将任何 SwiftUI 视图渲染为 PDF,所有文本和形状仍然是矢量的,它们可以完美地缩放。(iOS 16 中的改进功能)
使用 ImageRenderer
创建 PDF 需要八个步骤:
render()
方法来启动渲染代码CGContext
对象来处理 PDF 页面完成后,您将获得 PDF 的 URL地址,并且可以随意使用
这听起来工作量很大,下面是一个完整的示例,它使用 ShareLink
将视图呈现为要导出的 PDF,其中的注释与上面的说明相匹配:
@MainActor
struct ContentView: View {
var body: some View {
ShareLink("Export PDF", item: render())
}
func render() -> URL {
// 1: 指定要渲染的内容
let renderer = ImageRenderer(content:
Text("Hello, world!")
.font(.largeTitle)
.foregroundStyle(.white)
.padding()
.background(.blue)
.clipShape(Capsule())
)
// 2: 指定文件输出的路径
let url = URL.documentsDirectory.appending(path: "output.pdf")
// 3: 开始渲染
renderer.render { size, context in
// 4: Tell SwiftUI our PDF should be the same size as the views we're rendering
var box = CGRect(x: 0, y: 0, width: size.width, height: size.height)
// 5: Create the CGContext for our PDF pages
guard let pdf = CGContext(url as CFURL, mediaBox: &box, nil) else {
return
}
// 6: Start a new PDF page
pdf.beginPDFPage(nil)
// 7: Render the SwiftUI view data onto the page
context(pdf)
// 8: End the page and close the file
pdf.endPDFPage()
pdf.closePDF()
}
return url
}
}
private func saveImageToAlbum(_ image: UIImage) {
// 1.访问共享的照片库
PHPhotoLibrary.shared().performChanges {
// 2.在变更的块中执行保存操作
PHAssetCreationRequest.creationRequestForAsset(from: image)
} completionHandler: { success, error in
// 3.处理保存操作的完成情况(如果失败,打印出错误信息)
if !success, let error = error {
print("Error saving image to album: \\(error.localizedDescription)")
}
}
}
PHPhotoLibrary.shared()
获取照片库的单例实例,这样可以与用户相册进行交互performChanges
块是执行保存图片操作的地方。这里使用 PHAssetCreationRequest.creationRequestForAsset(from:)
来创建一个新的照片资产,使用要保存的 UIImage
对象作为参数completionHandler
闭包在保存操作完成时被调用,它允许我们处理保存操作的结果,无论是成功还是失败。它提供了两个参数: success
(一个布尔值,表示操作是否成功)和 error
(如果操作失败,则为一个可选的 Error
对象)success
参数为 false
,则说明保存操作失败了。在这种情况下,使用 error
对象的 localizedDescription
属性打印出一条可读的错误信息