How to save/export an image in Mac Catalyst

Catalyst

App Sandbox #

The first thing we do to do is enable Read/Write permission for User Selected File. To do that:

  1. Click on your app name under "TARGETS".
  2. Select "Signing & Capabilities" tab.
  3. Scroll down to "App Sandbox" section.
  4. Under "File Access", change "Permission & Access" of "User Selected File" from None to Read/Write.
Set read/write permission for
Set read/write permission for "User Selected File" in App Sandbox

This will give us access to writing files outside of our app sandbox, which is essential for users to select a saving destination for their images.

Code #

We now have everything ready. Let's jump into code.

func export(image: UIImage) {
guard let imageData = image.pngData() else { // 1
return
}

let fileManager = FileManager.default // 2

do {
let fileURL = fileManager.temporaryDirectory.appendingPathComponent("temp.png") // 3

try imageData.write(to: fileURL) // 4

if #available(iOS 14, *) {
let controller = UIDocumentPickerViewController(forExporting: [fileURL]) // 5
present(controller, animated: true)
} else {
let controller = UIDocumentPickerViewController(url: fileURL, in: .exportToService) // 6
present(controller, animated: true)
}
} catch {
print("Error creating file")
}
}

<1> Convert UIImage to Data of png format.
<2> Get a reference of FileManager.
<3> We can't save our image data directly to an external location. So, we have to save it to our app sandbox directory first, then export it out. In this case, I name it temp.png and save it to a temporary directory. The filename (temp) will use to prefill in saving dialog (<5>, <6>).
<4> Save our image to temp URL.
<5>, <6> Create a document picker view controller with an initializer for exporting. In iOS 14, Apple deprecate init(url: URL, in mode: UIDocumentPickerMode) and replacing it with init(forExporting urls: [URL]).

Run this code and try to export an image. You will be presented with a document picker to choose your saving destination.

A document picker for exporting
A document picker for exporting

Be a good Mac citizen #

After the export, there is no use for our original temp file. We should be a good OS citizen and delete that file for good. In our example, we save a file to a temporary directory, which will be purged automatically by the system. If you don't want to rely on the system purging or save a file in other locations, the following is how you can delete it.

Prior iOS 14 #

We set a delegate and listen to callbacks. Then we remove our temp file within callback methods.

let controller = UIDocumentPickerViewController(url: fileURL, in: .exportToService)
controller.delegate = self

// MARK: - UIDocumentPickerDelegate
func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) {
let fileManager = FileManager.default

let fileURL = fileManager.temporaryDirectory.appendingPathComponent("temp.png")
do {
try FileManager.default.removeItem(at: fileURL)
} catch {
print("Error deleting file")
}
}

func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
let fileManager = FileManager.default

let fileURL = fileManager.temporaryDirectory.appendingPathComponent("temp.png")
do {
try FileManager.default.removeItem(at: fileURL)
} catch {
print("Error deleting file \(error)")
}
}

iOS 14 #

With iOS 14, we don't need to delete the file in documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]). Because the original document will be moved (removed from the original location) to the selected destination when we initialize UIDocumentPickerViewController with init(forExporting urls: [URL]). So, we only need to delete the file in cancel callback.

let controller = UIDocumentPickerViewController(forExporting: [fileURL])
controller.delegate = self

// MARK: - UIDocumentPickerDelegate
func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) {
let fileManager = FileManager.default

let fileURL = fileManager.temporaryDirectory.appendingPathComponent("temp.png")
do {
try FileManager.default.removeItem(at: fileURL)
} catch {
print("Error deleting file")
}
}

Feel free to follow me on Twitter and ask your questions related to this post. Thanks for reading and see you next time.

If you enjoy my writing, please check out my Patreon https://www.patreon.com/sarunw and become my supporter. Sharing the article is also greatly appreciated.

Become a patron Tweet Share

If you enjoy this article, you can subscribe to the weekly newsletter.

Every Friday, you’ll get a quick recap of all articles and tips posted on this site — entirely for free.

← Home