UIDocument + NSFileWrapper + iCloud = Strange Behaviour - ios

I’m trying to write a fairly simple UIDocument based app and I want the data for each document to be a parent directory containing some image files.
To get started I chose to use Apple’s ShapeEdit sample as that seems to handle lots of the complexity of having files in iCloud. It does only use a single plist file for it’s document data though.
As a first step I got the ShapeEdit app up and running on my iPad and everything works as expected. I have the app and iCloud Drive app running in split view on my iPad so I can see the files ShapeEdit is creating. As expected each time I add a new document a new file appears in iCloud Drive.
To migrate the ShapeEdit app to use more files I moved over to using NSFileWrapper. When ShapeEdit creates a new document it copies a template file. I changed this to just create a new folder using
try fileManager.createDirectoryAtURL(writeIntent.URL, withIntermediateDirectories: false, attributes: nil)
I then modified the UIDocument code to have it read from a NSFileWrapper like this
override func loadFromContents(contents: AnyObject, ofType typeName: String?) throws {
guard let data = contents as? NSFileWrapper else {
fatalError("Cannot handle contents of type \(contents.dynamicType).")
}
fileWrapper = data
I also hobbled some of the other functions in the document to return a simple image for thumbnails, just to get it running and set LSTypeIsPackage to true. Finally I left the shape selection UI there to trigger a document update with
updateChangeCount(.Done)
Again this all works fine. Creating a new document creates a new Directory named Untitled.shapeFile and it shows up in iCloud. Now for odd thing number one. If I open that document and tap on the shape selection UI to trigger a change and wait a few seconds another Untitled.shapeFile directory shows in iCloud. In fact every time I do this I get another one. These seem to only be local to the iPad though as iCloud on my mac only has one copy. Deleting any of these directories within the iCloud Drive app causes all of them to be deleted.
For the next step I added little bit of code to the UIDocument loadFromContents() function
fileWrapper?.addRegularFileWithContents(UIImagePNGRepresentation(image!)!, preferredFilename: "MYImage.png")
so every time the document is loaded it adds another image file to the parent directory.
To begin with this does exactly as I hoped. Open the doc, tap the shape select UI see the MyImage.png file turn up in iCloud Drive inside Untitled.shapeFile. Keep closing and opening and I get more MyImage.png files with some junk on the front which, I assume, is iCloud’s way of preventing files with the same name so all is well there. Except for strange thing number two. Shortly after the MYImage.png file shows in iCloud Drive another one pops up called MyImage 2.png. If I keep closing and opening all the versions of MyImage also get a copy with a 2. My edited version of ShapeEdit never adds any image files with this name format. These extra images do show up on the mac as well so they aren't the same strange phantom copies as the main document directory.
So two things
1) Why do I get multiple copies of the Untitled.shapeFile doc which seem to actually be the same thing?
2) Why do I get extra copies of my image file with a 2 added that my app never had anything to do with?
Any suggestions welcome before this drives me crackers.

Related

Flutter cannot retrieve iPhone file (sometimes)

I have an app that lets the user pick local files and send them to the backend to add to their vault. To pick the file, I use FilePicker.platform.pickFiles. It seems to work fine, the file is picked and when it's an image we show a preview to the user (and it always works).
Once the user has picked all their files, we create a list of Future to try and send the files to the backend, then we await the list to resolve. If we deem a picture is too big, we reduce the size using FlutterImageCompress.compressAndGetFile.
This usually works, but on iPhone, there is a seemingly random behavior where the file isn't found and an error is thrown : Cannot retrieve length of file, path = '/private/var/mobile/Containers/Data/Application/3600F812-FD42-4441-A654-766991E45E04/tmp/com.feykro.myApp.int-Inbox/myFile.pdf' (OS Error: No such file or directory, errno = 2)
The file in the example works just fine in other instances, the problem seems random and not directly tied to the file I chose. The problem is not exclusive to pdf files either, as it also occurs on images even though they're picked correctly and displayed in the app.
Any idea what could be causing this ?
Thanks in advance !

iCloud Drive cannot remove file created from other device

I'm trying to adopt iCloud Drive to store backups of the data of my application using a single file for each backup, if it's relevant they're simple XML files with a custom extension. File creation and upload are working fine but as I'm now trying to let the user manually delete backups I found out that I cannot delete the file programmatically but I can do it if I go to iCloud Drive from the app on iOS or the folder in Finder on macOS.
To save the files I first retrieve the root for the container with
FileManager.default.url(forUbiquityContainerIdentifier: nil)?.appendingPathComponent("Documents")
and create the Document folder if it doesn't exists as I want the user to be able to view and edit the files, after creating the file locally in the temporary directory I upload it with
let file = FileManager.default
if file.fileExists(atPath: remotePath.path) {
try? file.removeItem(at: remotePath)
}
try? file.setUbiquitous(true, itemAt: localPath, destinationURL: remotePath)
where remotePath is the path retrieved before with the file name appended. To load the files I use a NSMetadataQuery and get the path of each file with NSMetadataItemURLKey on each returned item, which is working fine but when I attempt to
try? FileManager.default.removeItem(at: retrievedPath)
it works if the same device created the file but if it is from another one it always fail and the error says that the file does not exists at the specified path.
I've removed error handling code for clarity but I've tested this behaviour inspecting the thrown error.
How can I delete any file even from other devices, am I missing some steps?
I was indeed missing something, after reading this question, in particular this answer, I found out that even if the NSMetadataQuery returns the path correctly is possible that the file has not been downloaded to the the device, hence the error.
To correctly delete a file you have to mimic the behaviour of UIDocument and use a NSFileCoordinator on a background queue, refer to the answer referenced before for implementation details.

hidden files with .icloud extension in ubiquity container

I am building app which syncs documents over the iCloud.
When I create document (lets say its name is myfile1.doc) in one device and expect to appear in another one I get nothing - it looks like it doesn't sync. To my surprise when I read the content of Documents folder in ubiquity container I can see the representation of the file is added as a hidden file with .icloud extension (in this case .myfile1.doc.icloud).
I tried to find more about this .icloud files but so far no results.
Anyone know what are they representing? Is it the metadata item that indicates that file was added to iCloud and I should manually download it?
I think hidden files are handled by metadataQuery to indicate that the new file was added to iCloud and is waiting to be sync'ed. NSMetadataQuery recognizes them as representation of new files not yet downloaded to ubiquity container.
These files are not documented and you should ignore them. If you want to know what's in them, open one in a hex editor and have a look. Your code should not check for them or access them in any way.

xCode: Retrieving images from apps documents directory

I've come across a problem I can't find a solution to. I've searched the forums extensively but can't find anything. Hopefully someone might be able to shed some light.
I have an app that can save images. They are saved to the apps documents directory and the path to them is saved in a sqlite database. Every time I run/build the app, it can no longer retrieve the images that are already in the documents directory. During that same build phase I can re-save the images and then I can retrieve them just fine, but as soon as I run/build the app again it can't retrieve the images. The images are not being deleted and the path is still correct.
This is how I try retrieving them in my ViewDidLoad method
//database was opened. Getting path to image
NSString *logoPath = [[NSString alloc] initWithUTF8String:(const char *) sqlite3_column_text(statement, 21)];
//use path to load image to a UIImage view
[self.logoView setImage:[UIImage imageWithContentsOfFile:logoPath]];
I have confirmed that the image is in the documents directory still and the path is correctly assigned in my first line of code above. This code only fails when I run/build the app. Once the app is running I can save images to the directory and retrieve them with this same code just fine, but once I run/build the app again, the images I saved on the previous build can't be pulled up even though I can see them in the directory still (I use iExplorer to view my iPhones directories).
Any ideas? Im thinking that maybe I have to initiate those images in the directory somehow in order to use them right after I build.
UPDATE - Some more info in case it will help. After the app is built on my iPhone, I can close the app and reopen it and the images work just fine. I can even completely close the app (double click home button and end app process) and when I relaunch the app the images are retrieved from the directory just fine. But as soon as I build the app again through xCode, I cannot access the images. Something is changing when a new build is done.
UPDATE 2 - I think I found the issue and it is in the path.
This is my path
/var/mobile/Containers/Data/Application/8A387845-7B1F-4228-9E7E-2498EC302C5A/Documents/RYal7UXrLvcompanyLogo.png
After I build the app again, this is now the new path when I save the image again
/var/mobile/Containers/Data/Application/C0C30E2F-05D8-48F4-A0FF-603359AF5130/Documents/RYal7UXrLvcompanyLogo.png
Looks like the part in between Applications and Documents changes after every build so the path I am saving in my sqlite db is no longer valid after a new build. So now I need to figure out what that middle part is that keeps changing and how to get it after each build so I can update my paths to my images.
The issue was the path. Every time a new build was created, part of the path to the documents directory changed. See UPDATE 2 in the original question above.

Saving xml file on iOS (pugiXml and Cocos2d-x)

Recently I've ran into a big problem with pugiXml (used within cocos2d-x engine).
Shortly, I've made a quiz game (in mentioned Cocos2d-x). I keep my questions (and some other data) in an Xml file. On new game they get parsed and inserted into dictionaries. If a question is answered, a short string indicating the answer (whether it was good or not - Y/N) is inserted into that Xml file (below that particular question). Later, I use this data to show statistics (I count the percentage of questions with good answers - which is counting the amount of Y's divided by the amount of questions, multiplied by 100).
I use:
CCFileUtils::sharedFileUtils()->fullPathForFilename(o_QA);
to get the file and later
pBuffer = CCFileUtils::sharedFileUtils()->getFileData(fullPath.c_str(), "rb", &bufferSize);
to put file into buffer and
pugi::xml_parse_result result = doc.load_buffer(pBuffer,bufferSize);
to parse data and start working on it.
Finally, I save the file with:
doc.save_file(fullPath.c_str());
Android:
It's working very well, although I had to copy the file with questions to /data/data/app/Files/ . Still, It's keeping the changes on many devices.
iOS:
Unfortunately, on iOS it's not working. Data get loaded and parsed (which means, that I can actually play the game), but they are not saved. I've tried moving the files to other folders (started from Resources/Documents, then main Resources folder, Resources/Library/Application Support). It's still not saving the data and I don't know what to do. The result is my statistics not being counted well (it doesn't matter how you answer the questions - all of them are false, because the Xml file is not being updated).
Did anyone run into similar problem?
Can you, please, help me?
I am doing something similar in my own app, using both pugixml and cocos2d-x. So I can confirm this combination works well.
Rather, because on iOS you cannot both read and write the data from and to the app bundle (which is read only), you will need to implement a simple check in the writable document directory - If you a saved file there, load it, if not, load from the app bundle.
So in essence for loading, if your saved filename is "my_save.xml", here is an example flow:
1) Construct a path for your save file in the writable folder by concatenating the writable folder path + your filename. CCFileUtils should have something like getWritablePath() for that.
2) if the file exists in the folder, load it. Otherwise, go to 3).
3) Construct a path to your original data file from the app bundle, using CCFileUtils::sharedFileUtils()->fullPathForFilename(). Load the file from there.
For saving, simply do step 1 and save the file there.

Resources