Mane Manero
Mane Manero

Reputation: 3778

SwiftUI:.fileExporter exporting only 1 document

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

Answers (1)

kamillo
kamillo

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

Related Questions