iOS File Saving Issue

I’m porting my macOS based Juce application to iOS. Everything is working great in the simulator but on the device (iPad with iOS 16.7), I fail to be able to save files.

The TemporaryFile::overwriteTargetFileWithTemporary fails with the assert indicating that the temp file could not be written.

I see an empty (0 bytes) file written to the location that I am trying to save to but the temp file that is going to get swapped into this location is failing to write.

The path to the temp file is…

/private/var/mobile/Containers/Shared/AppGroup/820E4D65-2C60-4742-A2A5-9373D6D27914/File Provider Storage/MMB/Jjjj_temp6fa40e3c.mmbtmp

It must have something to do with permissions. One of the errors I see when experimenting is…

Printing description of fout:
(juce::FileOutputStream) fout = {
juce::OutputStream = {
newLineString = {
text = (data = “\r\n”)
}
leakDetector237 = {}
}
file = {
fullPath = {
text = (data = “/private/var/mobile/Containers/Shared/AppGroup/820E4D65-2C60-4742-A2A5-9373D6D27914/File Provider Storage/MMB/Asdf.mmbcfg”)
}
}
fileHandle = 0x0000000000000000
status = {
errorMessage = {
text = (data = “Operation not permitted”)
}
}
currentPosition = 0
bufferSize = 8192
bytesInBuffer = 0
buffer = (data = “”)
leakDetector123 = {}
}

I have Support Document Browsing, File Sharing, Content Sharing, and iCloud Permissions all enabled.

Here’s a subset of my plist…

<key>UIFileSharingEnabled</key>
<true/>
<key>UISupportsDocumentBrowser</key>
<true/>

Does anyone have insight to what might be going on?

In iOS all Apps are sandboxed and you’re allowed to write files only inside your sandbox, so it’s ok to write a file like this:

File f(File::getSpecialLocation(File::userApplicationDataDirectory).getFullPathName() + File::getSeparatorString() + "myDataFile.txt");
if (f.create().ok())
	f.replaceWithText("Hello world");

Don’t bother with absolute paths, let Juce find the correct path for you.

However, if you want to write outside your App or on the iCloud, you need to use URL instead:

fileChooser.reset(new FileChooser("Write a file", File(), "*.txt", true, false, this));
fileChooser->launchAsync(FileBrowserComponent::saveMode | FileBrowserComponent::canSelectFiles | FileBrowserComponent::warnAboutOverwriting,
[&](const juce::FileChooser& chooser)
{
	if (chooser.getURLResults().size() > 0)
	{
		auto url = fileChooser->getURLResult();
		
		// Make sure to overwrite
		if (url.getLocalFile().existsAsFile()) 
			url.getLocalFile().deleteFile();
		
		// Get the outputStream and write something...
		auto outputStream = url.createOutputStream();
		outputStream->writeString("Hello World");
	}
});
2 Likes