在 iOS 16 / macOS 13 之前,将任何一个 View 转为 image 需要借助 NSHostingController
或 UIHostingController
编写相关代码实现,而随着 Apple 在 iOS 16.0 / macOS 13 后发布了新的 API — ImageRenderer 后,仅需一行代码就能将一个 View 转为 image。
下面我们将借助ImageRenderer
类将一个 ScrollView 中的 content 转为 image,即使 content 的高度高于屏幕高度,这种方法仍然可将全部的 content 转为 image。
假设我们有下面一个 ScrollView:
import SwiftUI
struct ContentView: View {
let fruits = ["Apple", "Banana", "Cherry"]
var body: some View {
VStack {
Text("Fruits")
.font(.largeTitle)
ScrollView {
VStack {
ForEach(fruits, id: \.self) { fruit in
Text(fruit)
}
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
在这一步中,我们将 ScrollView 中 content 提取为一个子 View
import SwiftUI
struct ContentView: View {
let fruits = ["Apple", "Banana", "Cherry"]
var body: some View {
VStack {
Text("Fruits")
.font(.largeTitle)
ScrollView {
InsideContentView(fruits: fruits)
.background(
ZStack {
GeometryReader {proxy in
Color.clear
.onAppear {
print(proxy.size)
}
}
}
)
}
}
}
}
struct InsideContentView: View {
var fruits: [String]
var body: some View {
VStack {
ForEach(fruits, id: \.self) { fruit in
Text(fruit)
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
import SwiftUI
struct ContentView: View {
let fruits = ["Apple", "Banana", "Cherry"]
@State var isTakeSnapshot: Bool = false
@State var snapshot_proxy: [GeometryProxy] = []
var body: some View {
VStack {
Text("Fruits")
.font(.largeTitle)
ScrollView {
InsideContentView(fruits: fruits)
.background(
ZStack {
GeometryReader {proxy in
Color.clear
.onChange(of: isTakeSnapshot) { _ in
snapshot_proxy = []
snapshot_proxy.append(proxy)
}
}
}
)
}
Button("take snapshot", action: {
isTakeSnapshot.toggle()
let snapshot_size = snapshot_proxy[0].size
let content = InsideContentView(fruits: fruits).frame(width: snapshot_size.width)
let renderer = ImageRenderer(content: content)
let iamge = renderer.nsImage ?? NSImage()
// 在这里使用 image, 保存、显示或分享等
})
}
}
}
struct InsideContentView: View {
var fruits: [String]
var body: some View {
VStack {
ForEach(fruits, id: \.self) { fruit in
Text(fruit)
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Button 中 action 的代码就是我们的实现部分。
有以下几点需要注意的地方:
.frame(width: snapshot_size.width)
保证截图宽度与 content 宽度一致,macOS 上不加 frame 修饰时,截图宽度可能会与 app 窗体宽度不一致。renderer.scale = 2.0
,表示将截图分辨率提升 2 倍,详情参加官方文档。