iOS 16 為 SwiftUI 帶來的另一個 API 就是 ImageRenderer
。有了這個 API,我們可以輕鬆把 SwiftUI 視圖轉換為圖像。這個實作十分簡單,讓我們利用想要轉換為圖像的視圖,來實例化 ImageRenderer
的實例:
let renderer = ImageRenderer(content: theView)
然後,我們就可以存取 cgImage
或 uiImage
屬性,來取得轉換後的圖像。
一如以往,我喜歡利用範例來示範一個 API 的用法。在第38章中,我們用了新的 Charts 框架來構建折線圖。這次,讓我們來看看如何讓使用者把折線圖保存為 Photo Album 中的圖像,並使用 ShareLink
進行分享。
首先,讓我們來重溫一下 ChartView
範例。我們使用了 Charts
框架的新 API 來構建一個有關氣溫的折線圖。程式碼如下:
var body: some View {
VStack {
Chart {
ForEach(chartData, id: \.city) { series in
ForEach(series.data) { item in
LineMark(
x: .value("Month", item.date),
y: .value("Temp", item.temperature)
)
}
.foregroundStyle(by: .value("City", series.city))
.symbol(by: .value("City", series.city))
}
}
.chartXAxis {
AxisMarks(values: .stride(by: .month)) { value in
AxisGridLine()
AxisValueLabel(format: .dateTime.month(.defaultDigits))
}
}
.chartPlotStyle { plotArea in
plotArea
.background(.blue.opacity(0.1))
}
.chartYAxis {
AxisMarks(position: .leading)
}
.frame(width: 350, height: 300)
.padding(.horizontal)
}
}
要跟著本教程學習,我建議你先從 https://www.appcoda.com/resources/swiftui5/SwiftUIImageRendererStarter.zip 下載 Xcode 項目。 在使用 ImageRenderer 之前,讓我們將上面在 ContentView.swift 中的程式碼重構為一個單獨的視圖,如下所示:
struct ChartView: View {
let chartData = [ (city: "Hong Kong", data: hkWeatherData),
(city: "London", data: londonWeatherData),
(city: "Taipei", data: taipeiWeatherData)
]
var body: some View {
VStack {
Chart {
ForEach(chartData, id: \.city) { series in
ForEach(series.data) { item in
LineMark(
x: .value("Month", item.date),
y: .value("Temp", item.temperature)
)
}
.foregroundStyle(by: .value("City", series.city))
.symbol(by: .value("City", series.city))
}
}
.chartXAxis {
AxisMarks(values: .stride(by: .month)) { value in
AxisGridLine()
AxisValueLabel(format: .dateTime.month(.defaultDigits))
}
}
.chartPlotStyle { plotArea in
plotArea
.background(.blue.opacity(0.1))
}
.chartYAxis {
AxisMarks(position: .leading)
}
.frame(width: 350, height: 300)
.padding(.horizontal)
}
}
}
然後,讓我們宣告一個變數來保存視圖:
var chartView = ChartView()
現在,我們可以把 Chart 視圖轉換為圖像了。我們要添加一個名為 Save to Photos 的按鈕,來把 Chart 視圖圖像儲存到 Photo Album 中。
讓我們如此實作按鈕:
var body: some View {
VStack(spacing: 20) {
chartView
HStack {
Button {
let renderer = ImageRenderer(content: chartView)
if let image = renderer.uiImage {
UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil)
}
} label: {
Label("Save to Photos", systemImage: "photo")
}
.buttonStyle(.borderedProminent)
}
}
}
在按鈕的閉包中,我們利用 chartView
來建立一個 ImageRenderer
的實例,並使用 uiImage
屬性來擷取生成的圖像。然後,讓我們調用 UIImageWriteToSavedPhotosAlbum
把圖像儲存到 Photo Album 中。
備註:我們需要在 info.plist 中添加一個 key Privacy - Photo Library Usage Description,讓 App 可以把圖像儲存到內置的 Photo Album 中。
在之前的章節,我們學了如何使用 ShareLink
來顯示一個 Share Sheet。我們可以搭配 ImageRanderer
使用,輕鬆地建立讓使用者分享 Chart 視圖的功能。
為方便起見,讓我們將渲染圖像 (rendered image) 的程式碼重構為一個獨立的方法:
@MainActor
private func generateSnapshot() -> UIImage {
let renderer = ImageRenderer(content: chartView)
return renderer.uiImage ?? UIImage()
}
這個 generateSnapshot
方法會把 chartView
轉換為圖像。
備註:如果你沒有用過 @MainActor
,可以參考這篇文章。
有了這個 helper 方法,我們就可以如此在 VStack
視圖中建立一個 ShareLink
:
ShareLink(item: Image(uiImage: generateSnapshot()), preview: SharePreview("Weather Chart", image: Image(uiImage: generateSnapshot())))
.buttonStyle(.borderedProminent)
現在,當我們點擊 Share 按鈕,App 就會擷取折線圖,並讓我們以圖像形式分享圖表。
你可能會發現渲染圖像的解析度有點低。ImageRenderer
類別有一個 scale
屬性,用來調整渲染圖像的比例。在預設情況下,這個屬性的值是 1.0。如果我們想提高圖像的解析度,可以將它設置為 2.0
或 3.0
。或者,我們可以將值設置為螢幕比例:
renderer.scale = UIScreen.main.scale
有了 ImageRenderer
類別,我們就可以簡單地把任何 SwiftUI 視圖轉換成圖像。如果你的 App 支援 iOS 16 以上的版本,就可以用這個新 API 為使用者建立一些方便的功能。除了渲染圖像外,我們還可以使用 ImageRenderer
來渲染 PDF 文件。詳情可以參閱 Apple 的官方文件。
在本章所準備的範例檔中,有最後完整的 Xcode 專案,可供你下載參考: