I'm trying to generate some images from SwiftUI views, and then export them to the filesystem. I've looked online and can't seem to get things to work. Tapping the file export button does nothing.
Should I be converting the images to a different format? I feel like I'm missing something obvious, this is harder than it should be.
struct Temp: View {
let screenshots: [ScreenshotModel]
let size: CGSize
@State private var isShowingExport = false
@State private var exportImages = [UIImage]()
var body: some View {
ScrollView {
VStack {
ForEach(exportImages, id: \.self) {
Image(uiImage: $0)
}
Button("Export to Files", systemImage: "folder") {
isShowingExport.toggle()
}
}
}
.task {
await generateImages()
}
.fileExporter(
isPresented: $isShowingExport,
items: exportImages.map { $0.pngData()! }
) { result in
switch result {
case .success:
print("Success")
case .failure(let error):
print(error)
}
} onCancellation: { }
}
func generateImages() async {
exportImages = await withTaskGroup(of: UIImage.self) { group in
for screenshot in screenshots {
group.addTask {
await generateImage(screenshot: screenshot)
}
}
var allImages = [UIImage]()
for await image in group {
allImages.append(image)
}
return allImages
}
}
@MainActor
func generateImage(screenshot: ScreenshotModel) -> UIImage {
let content = ScreenshotPreviewView(
screenshotModel: screenshot,
size: size
).frame(size: size)
let renderer = ImageRenderer(content: content)
return renderer.uiImage!
}
}
You could use this approach using .fileExporter(isPresented: ..., documents: ...)
and ImageDocument: FileDocument
as shown.
Example code:
Note in particular the fw.filename
to give each file a different name.
struct ContentView: View {
var body: some View {
// for testing
Temp(screenshots: [ScreenshotModel(name: "house"),ScreenshotModel(name: "globe")], size: .zero)
}
}
// for testing
struct ScreenshotModel: Identifiable {
let id = UUID()
var name: String
}
struct Temp: View {
let screenshots: [ScreenshotModel]
let size: CGSize
@State private var isShowingExport = false
@State private var exportImages = [UIImage]()
var body: some View {
ScrollView {
VStack {
ForEach(exportImages, id: \.self) {
Image(uiImage: $0)
}
Button("Export to Files", systemImage: "folder") {
isShowingExport.toggle()
}
}
}
.task {
await generateImages()
}
.fileExporter(
isPresented: $isShowingExport,
documents: exportImages.map{ImageDocument(image: $0)} // <-- here
) { result in
switch result {
case .success:
print("-----> Success")
case .failure(let error):
print(error)
}
} onCancellation: { }
}
func generateImages() async {
exportImages = await withTaskGroup(of: UIImage.self) { group in
for screenshot in screenshots {
group.addTask {
await generateImage(screenshot: screenshot)
}
}
var allImages = [UIImage]()
for await image in group {
allImages.append(image)
}
return allImages
}
}
@MainActor
func generateImage(screenshot: ScreenshotModel) async -> UIImage { // <-- here
// let content = ScreenshotPreviewView(
// screenshotModel: screenshot,
// size: size
// ).frame(size: size)
//
// let renderer = ImageRenderer(content: content)
//
// return renderer.uiImage!
// for testing
return UIImage(systemName: screenshot.name)!
}
}
struct ImageDocument: FileDocument {
static var readableContentTypes: [UTType] { [.png] }
var image: UIImage
init(image: UIImage?) {
self.image = image ?? UIImage()
}
init(configuration: ReadConfiguration) throws {
guard let data = configuration.file.regularFileContents,
let image = UIImage(data: data)
else {
throw CocoaError(.fileReadCorruptFile)
}
self.image = image
}
func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper {
let fw = FileWrapper(regularFileWithContents: image.pngData()!)
fw.filename = String(UUID().uuidString.prefix(6)) // <--- for testing
return fw
}
}