My app creates some back-up files as Packages (in fact as directories with an extension, and an Exported UTI that conforms to com.apple.package).
I would like to be able to read them as NSData, that I can attach to an email in a MFMailComposeViewController. Actually, it doesn't work because dataWithContentsOfURL: returns nil when I try to read the package (I think because it's a directory, not a regular file).
I know my package files are fine because I can access them on my Mac when I download the "app container" from my iPhone.
I generate them using NSFileWrappers, and calling the writeToURL:options:originalContentsURL:error: method.
I don't want to use serializedRepresentation because it generates NSData that can be read only by NSFileWrapper (and I would like to be able to open them on my Mac, by clicking on "Show Packages Contents").
Here is the part of code that doesn't work:
NSURL *finalBackupURL = [outputDirectoryURL URLByAppendingPathExtension:#"ext"];
if (![packageWrapper writeToURL:finalBackupURL options:0 originalContentsURL:nil error:error])
#throw [NSException exceptionWithError:*error];
NSData *data = [NSData dataWithContentsOfURL:finalBackupURL];
(data = nil whereas the file has well been created)
Thank you for your help
Related
We have an iOS application that manages documents via Core Data. The actual files reside in the app's shared container so that the app's file provider extension can also access them for Files.app support. We want to give the user the option to open these files in third-party apps so that they can edit them in-place instead of sending a copy to the other app.
We provide a UIActivityViewController for sharing files with other apps. We also provide a UIActivity that shows a UIDocumentInteractionController which seems to work better in some cases. We give the UIActivityViewController the document's file URL, the raw text content, and printable data.
This works but all third-party editors are shown as Copy to … instead of Open in …
We've also set the UIFileSharingEnabled and LSSupportsOpeningDocumentsInPlace properties to YES in the app's info.plist but they seem to be only relevant for open-in-place when sharing files residing in the app's Documents folder.
Now we've stumbled upon the NSItemProviderFileOptionOpenInPlace option for NSItemProvider. As we're already supporting a file provider extension and from Apple's documentation this seemed like a great place to accomplish just what we want.
Adding a "pure" NSItemProvider works, in a way, but shows fewer options than when also sharing the file URL and text in addition (which is expected). However, when we use -[NSItemProvider registerFileRepresentationForTypeIdentifier:fileOptions:visibility:loadHandler:] with the said option (or just zero, same result) and return the file URL in the loadHandler's completionHandler() nothing is shared anymore. E.g., Mail no longer attaches the file, Messages doesn't show the document for sending.
These are the relevant bits of the code:
NSMutableArray *items = [NSMutableArray array];
NSMutableArray <UIActivity *> *activities = [NSMutableArray array];
NSURL *fileURL = self.record.metadata.fileURL;
NSString *fileUTI = self.record.metadata.uti;
NSItemProvider *itemProvider = [[NSItemProvider alloc] initWithItem: fileURL typeIdentifier: fileUTI];
[itemProvider registerFileRepresentationForTypeIdentifier:fileUTI fileOptions:NSItemProviderFileOptionOpenInPlace visibility:YES loadHandler:^NSProgress * _Nullable(void (^ _Nonnull completionHandler)(NSURL * _Nullable, BOOL, NSError * _Nullable))
{
if (fileURL)
completionHandler(fileURL, YES, nil);
else
completionHandler(nil, YES, [NSError errorWithDomain:NSCocoaErrorDomain code:NSURLErrorFileDoesNotExist userInfo:nil]);
return nil;
}];
[items addObject:itemProvider];
self.activityViewController = [[UIActivityViewController alloc] initWithActivityItems:items applicationActivities:activities];
[UIAppDelegate.splitViewController presentViewController:self.activityViewController animated:YES completion:nil];
The using the Share menu the item provider's load handler is correctly called and the file's actual URL returned.
Is that not how NSItemProviderFileOptionOpenInPlace is intended to be used? Or are we using it simply wrong? Apple's description is extremely sparse and we couldn't find any information elsewhere on the internet except for the official documentation.
I've found out what my problem was: Not deep enough understanding of the relationship between the activity view controller and file providers.
As all my files reside in the shared container and are published also through the file provider extension, what I need to share through the activity view controller is the exact same URL that is shared through the file provider extension. Technically then the app that opens the file accesses it through there file provider mechanism.
I am trying to transition from Dropbox API v1 to v2. My objective is to upload video files to Dropbox in the app folder that Dropbox creates for the apps that do not require access to root folder. I checked this tutorial but have the following confusions:
NSData *fileData = [#"file data example" dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:NO];
// For overriding on upload
DBFILESWriteMode *mode = [[DBFILESWriteMode alloc] initWithOverwrite];
[[[client.filesRoutes uploadData:#"/test/path/in/Dropbox/account/my_output.txt"
mode:mode
autorename:#(YES)
clientModified:nil
mute:#(NO)
inputData:fileData]
setResponseBlock:^(DBFILESFileMetadata *result, DBFILESUploadError *routeError, DBRequestError *networkError) {
if (result) {
NSLog(#"%#\n", result);
} else {
NSLog(#"%#\n%#\n", routeError, networkError);
}
}] setProgressBlock:^(int64_t bytesUploaded, int64_t totalBytesUploaded, int64_t totalBytesExpectedToUploaded) {
NSLog(#"\n%lld\n%lld\n%lld\n", bytesUploaded, totalBytesUploaded, totalBytesExpectedToUploaded);
}];
What should be "/test/path/in/Dropbox/account/my_output.txt" in my case, as I do not access the root folder?
Whether the same code is supposed to work for binary files such as mp4 files (it uses UTF8 encoding in the sample code when preparing NSData)?
The "/test/path/in/Dropbox/account/my_output.txt" in the sample is just an example. You should supply the path for the desired location of the uploaded file in the Dropbox account. If you're using an app folder app, the root you supply will automatically be translated into the app folder itself. For example, if you have an app folder at "/Apps/MyAppName", and you want to upload a file named "video.mp4" into a folder called "Videos" in your app folder, you should supply a path value of "/Videos/video.mp4". That will automatically become /Apps/MyAppName/Videos/video.mp4 in the account.
The sample makes an NSData by encoding a string, but you can use the same uploadData to upload a file from any NSData.
Basically this path is /test/path/in/Dropbox/account/my_output.txt.
In dropbox account it will create folders like this test>path>in>Dropbox>account-- then your file will be in account folder. You can replace it with
/yourFolderName/Yourfilename.extension
I have a problem syncing NSFileWrapper documents with iCloud. I am able to create my wrapper and save it to my ubiquitous container.
When I try to read it from the device that created it, it works. When I try to read form another device that got it from iCloud, it crashes.
Some code:
This function to add a wrapper container with a NSString
- (void) addNSString:(NSString*)_string toFileWrapper:(NSFileWrapper*)_wrapper forKey:(NSString*)_key {
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:_string];
if(data) {
[_wrapper addRegularFileWithContents:data preferredFilename:_key];
}
}
And then here is how I decode it:
- (id) unarchiveObjectFromWrappers:(NSDictionary*)_wrappers withKey:(NSString*)_key {
id value = nil;
NSFileWrapper *wrapper = [_wrappers valueForKey:_key];
if(wrapper) {
NSData *data = [wrapper regularFileContents];
if(data) {
value = [NSKeyedUnarchiver unarchiveObjectWithData:data];
}
}
return value;
}
The decoding part works on one device and not on the others (EXC_BAD_ACCESS when the NSKeyedUnarchiver tries to unarchive from the NSData. The NSData seems good, it has the proper length and everything but when I try to log its datas for example it crashes).
My guess is that the NSFileWrapper doesn't download its full content, only its structure and that I have to do something to make it available. But I don't know what.
Any ideas?
========
Edit:
NSURLUbiquitousItemIsDownloadedKey says that the file is downloaded BUT if I try to copy it to the sandbox it fails with this error: "The operation couldn’t be completed. Bad file descriptor"
So the file is either not uploaded properly to iCloud or not downloaded properly...
It drove me crazy too. The solution is rather simple, yet totally undocumented by Apple. You must download the file specifically. Only the file wrapper is downloaded automatically, but not its contents. That's why the check says the file exists.
Before copying the file over, call something like this:
[[NSFileManager defaultManager]startDownloadingUbiquitousItemAtURL:cloudURL error:nil];
Related: Cannot sync simple text file with iCloud (bad file descriptor)
Is there a recommended way of testing for CSS file type in iOS?
I am asking for the NSURLTypeIdentifierKey thus:
NSError *err = nil;
NSString *uti = nil;
NSURL *fileURL = [NSURL fileURLWithPath:path];
[fileURL getResourceValue:&uti forKey:NSURLTypeIdentifierKey error:&err];
This works for most filetypes that I've tried it on, and I get back one of the Uniform Type Identifiers listed in the System-Declared Uniform Type Identifiers reference. I can test for RTF, etc, like this.
I noticed that for a CSS file I get back a UTI of “dyn.ah62d4rv4ge80g65x” though. I thought perhaps my CSS file had some odd metadata, so I cut and paste its contents into a brand new file in VI, and the UTI for that file came back the same.
I then did "cat /dev/null > empty.css" and tested the UTI of that empty file and it too came back with “dyn.ah62d4rv4ge80g65x”, so clearly this UTI is being derived from the file extension.
I don't see any mention of “dyn…” UTIs in the reference. Can I rely on this odd string?
Usually this indicates a dynamic UTI, that is one that the system creates on the fly as it is not part of its database. In such case you probably have to examine other attributes like file extension or look inside the file.
As IPA structure is just a zipped file containing compiled codes & media contents like images & audio, how can I protect the contents from being extracted and stolen by others? Is there any encryption I can add into the IPA?
This answer mentions that the application is already encrypted by the time it gets onto your users' devices: Does Apple modify iOS application executables on apps submitted to the App Store?
Sorry, that's only the application binary. The other media are not encrypted, and no, there's no way to encrypt the .ipa. You could try encrypting your images and other media on your system, providing a bunch of application code to decrypt those resources when the app runs, and then your decryption code will become a part of the encrypted application binary. You can't submit an encrypted IPA though, it needs to be the file directly output from Xcode.
In response to your comment, the one I've used in the past is CommonCrypto. You can use this crypto library as a starting point.
Simple usage example of the above:
NSError *error;
NSMutableData *encryptedData = [NSMutableData dataWithContentsOfFile:pathToEncryptedFile];
NSData *decryptedData = [RNDecryptor decryptData:encryptedData
withPassword:#"SuperSecretDecryptionKey"
error:&error];
UIImage *decryptedImage = [UIImage imageWithData:decryptedData];
IMPORTANT NOTE HERE: IF someone was to run the strings utility on your .app on a jailbroken iphone, or even on an iPhone they have filesystem access to via USB, they will get a list of all strings declared in your app. This includes "SuperSecretDecryptionKey". So you may want to use an integer, floating-point or other constant to do on-the-fly generation of a string decryption key, or make sure that the string you use to decrypt things is exactly the same as a normal system string so no-one suspects it as the true key. Security through obscurity, in this case, is advantageous.
To encrypt/decrypt *.strings files, you should encrypt the key and value strings in some manner (maybe one which gives you hexadecimal back, or any alphanumeric characters), and when you want to access a given value, say LicenceNumber, do this:
NSError *error;
NSData *unencryptedKey = [#"LicenceNumber"
dataUsingEncoding:NSUTF8StringEncoding];
NSData *encryptedKey = [RNEncryptor encryptData:unencryptedKey
withSettings:kRNCryptorAES256Settings
password:#"SuperSecretEncryptionKey"
error:&error]
NSData *encryptedValue = [[NSBundle mainBundle]
localizedStringForKey:[NSString
stringWithUTF8String:[encryptedKey bytes]]
value:#"No licence"
table:#"EncryptedStringsFile"];
NSData *decryptedValue = [RNDecryptor decryptData:encryptedValue
withPassword:#"SuperSecretDecryptionKey"
error:&error];