Reputation: 3778
PLATFORM AND VERSION iOS Xcode Version 12.4 (12D4e)
Goal: export multiple images using SwiftUI .fileExporter modifier.
Problem: On MacOS using NS Image, all images get exported. On iOS (iPad, Catalyst and iPhone) using UIImage, only 1 image gets exported
STEPS TO REPRODUCE
iOS code:
class AppContext: ObservableObject {
@Published var fileSaveDialogShown = false
}
@main
struct ExportApp: App {
@StateObject var appContext = AppContext()
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(self.appContext)
.fileExporter(
isPresented: $appContext.fileSaveDialogShown,
documents: [
ImageDocument(image: UIImage(named: "1")),
ImageDocument(image: UIImage(named: "2"))
],
contentType: .png // Match this to your representation in ImageDocument
) { url in
print("Saved to", url) // [URL]
}
}
}
}
import SwiftUI
import UniformTypeIdentifiers
struct ImageDocument: FileDocument {
static var readableContentTypes: [UTType] { [.jpeg, .png, .tiff] }
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 {
// You can replace tiff representation with what you want to export
return FileWrapper(regularFileWithContents: image.pngData()!)
}
}
struct ContentView: View {
@EnvironmentObject var appContext: AppContext
var body: some View {
VStack {
Button(action: {
appContext.fileSaveDialogShown.toggle()
}, label: {
Text("Button")
})
}
.frame(width: 200, height: 200)
}
}
MacOS code:
import SwiftUI
class AppContext: ObservableObject {
@Published var fileSaveDialogShown = false
}
@main
struct FocalApp: App {
@StateObject var appContext = AppContext()
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(self.appContext)
.fileExporter(
isPresented: $appContext.fileSaveDialogShown,
documents: [
ImageDocument(image: NSImage(named: "1")),
ImageDocument(image: NSImage(named: "2"))
],
contentType: .jpeg // Match this to your representation in ImageDocument
) { url in
print("Saved to", url) // [URL]
}
}
}
}
import SwiftUI
import UniformTypeIdentifiers
struct ImageDocument: FileDocument {
static var readableContentTypes: [UTType] { [.jpeg, .png, .tiff] }
var image: NSImage
init(image: NSImage?) {
self.image = image ?? NSImage()
}
init(configuration: ReadConfiguration) throws {
guard let data = configuration.file.regularFileContents,
let image = NSImage(data: data)
else {
throw CocoaError(.fileReadCorruptFile)
}
self.image = image
}
func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper {
// You can replace tiff representation with what you want to export
return FileWrapper(regularFileWithContents: image.tiffRepresentation!)
}
}
struct ContentView: View {
@EnvironmentObject var appContext: AppContext
var body: some View {
VStack {
Button(action: {
appContext.fileSaveDialogShown.toggle()
}, label: {
Text("Button")
})
}
.frame(width: 200, height: 200)
}
}
Upvotes: 1
Views: 1052
Reputation: 141
It seems that you need to give unique names for each of the exported files. Without explicite defining names, both images will be named "Exported PNG images" so the first one is going to be overwritten.
This works for me:
import SwiftUI
class AppContext: ObservableObject {
@Published var fileSaveDialogShown = false
}
@main
struct ExportApp: App {
@StateObject var appContext = AppContext()
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(self.appContext)
.fileExporter(
isPresented: $appContext.fileSaveDialogShown,
documents: [
ImageDocument(image: UIImage(named: "1"), name: "1"),
ImageDocument(image: UIImage(named: "2"), name: "2")
],
contentType: .png // Match this to your representation in ImageDocument
) { url in
print("Saved to", url) // [URL]
}
}
}
}
import SwiftUI
import UniformTypeIdentifiers
struct ImageDocument: FileDocument {
static var readableContentTypes: [UTType] { [.jpeg, .png, .tiff] }
var image: UIImage
var name: String
init(image: UIImage?, name: String) {
self.image = image ?? UIImage()
self.name = name
}
init(configuration: ReadConfiguration) throws {
guard let data = configuration.file.regularFileContents,
let image = UIImage(data: data)
else {
throw CocoaError(.fileReadCorruptFile)
}
self.image = image
self.name = configuration.file.filename ?? "image"
}
func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper {
// You can replace tiff representation with what you want to export
let fileWrapper = FileWrapper(regularFileWithContents: image.pngData()!)
fileWrapper.filename = self.name
return fileWrapper
}
}
struct ContentView: View {
@EnvironmentObject var appContext: AppContext
var body: some View {
VStack {
Button(action: {
appContext.fileSaveDialogShown.toggle()
}, label: {
Text("Button")
})
}
.frame(width: 200, height: 200)
}
}
Upvotes: 1