How to store file with local path in iOS - ios

I am a React native developer and I was trying to integrate a native ios code.
One of the instance takes NSURL * which should be a local path I guess
https://github.com/uber/startup-reason-reporter/blob/master/StartupReasonReporter/StartupReasonReporterPriorRunInfo/UBApplicationStartupReasonReporterPriorRunInfo.h#L21
+ (nonnull instancetype)priorRunAtDirectoryURL:(nullable NSURL *)directoryURL;
I am not sure what does localPath Url looks like in IOS, like what should I pass? for example?
Ps: intentionally including swift tag as well because I think swift developers could also answer it.

Based on description of that function:
Returns the prior run information stored to disk at the given directory URL.
#param directoryURL The directory to use to to store the startup reason data.
#return the previous startup reason data if it was present on disk, or empty startup reason object.
*/
you need to provide a directory they can write into. So Apps's document directory would be the best (as a root) + whatever folder you want (which, based on their code, they will even create for you).
So:
NSURL* docs = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
NSURL* myCrashes = [docs URLByAppendingPathComponent:#"myCrashes" isDirectory:TRUE];
it will look something like:
file:///some/path/to/app/sandbox/data/Documents/myCrashes

Try looking up documentation for
- (NSArray<NSURL *> *)URLsForDirectory:(NSSearchPathDirectory)directory
inDomains:(NSSearchPathDomainMask)domainMask;`
That will get you the URL and check out FileManager.SearchPathDirectory enum for all the viable options:
Here's an example for getting the caches directory
NSURL* url = [[[NSFileManager defaultManager] URLsForDirectory:NSCachesDirectory inDomains:NSUserDomainMask] lastObject];`
It could be any one of the 25-26 options in SearchPathDirectory depending on where they put that stuff.

Related

Accessing locally stored (hidden from user, under library) file in iOS

Here is the NSURL object that I am using for creating and accessing important configuration file on iOS with features:
hidden from user
not user generated file (storing configuration related to user)
not temp or cache (not possible to create later with existing data)
must be backed up by iCloud/iTunes
[NSURL fileURLWithPath:[[NSSearchPathForDirectoriesInDomains(
NSLibraryDirectory,
NSUserDomainMask,
YES) objectAtIndex:0]
stringByAppendingString:#"/important.txt"]];
As suggested in FileSystemOverview (developer.apple.com), I am storing this file under Library.
Maybe better way is storing it under Library/Application Support.
Using NSSearchPathForDirectoriesInDomains, which takes:
enum NSSearchPathDirectory
NSApplicationDirectory
NSDeveloperApplicationDirectory
NSLibraryDirectory
NSDeveloperDirectory
NSApplicationSupportDirectory
...
enum NSSearchPathDomainMask
NSUserDomainMask
NSLocalDomainMask
NSSystemDomainMask
...
BOOL expandTilde
Is this the correct way of storing such a file?
There are couple of alternatives for NSSearchPathDirectory and NSSearchPathDomainMask.
Also what about the expandTilde, on iOS is it necessary?
Is there a better way of doing it, instead of creating path as a NSString using objectAtIndex and appending file name then converting it to NSURL?
Thanks.
Using NSApplicationSupportDirectory would be my first choice for this.
But keep a few things in mind:
Unlike the "Documents" folder, the "Library/Application Support" folder doesn't exist in an iOS app sandbox by default so you must create the folder before trying to use it.
"Hidden from the user" only means that the user won't see it under normal circumstances. But the file is easily accessible by anyone with any technical knowledge.
You do want to pass YES for the expandTilde parameter so your app returns a proper path when you run the app in the simulator. On a real iOS device, it makes little difference.
Do not use stringByAppendingString: to create paths. Use stringByAppendingPathComponent:.
NSString *appSupportPath = [NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES) firstObject];
NSString *filePath = [appSupportPath stringByAppendingPathComponent:#"important.txt"];
NSURL *fileURL = [NSURL fileURLWithPath:filePath];
You can get a direct NSURL using NSFileManager:
NSURL *appSupportURL = [[[NSFileManager defaultManager] URLsForDirectory:NSApplicationSupportDirectory inDomains:NSUserDomainMask] firstObject];
NSURL *fileURL = [appSupportURL URLByAppendingPathComponent:#"important.txt"];

ObjectiveC - Reading ePub File Gotten From Dropbox

So I'm pretty lost with this one and really new to epub files. I've done a bit of searching but can't seem to put everything together in my head.
My app uses DropBox's Chooser API to get a file from a user's DropBox folder. In this case, I want to open up a .epub file. So when the user chooses a file, the DropBox API gives me back an NSURL object to that file. For example:
https://dl.dropboxusercontent.com/1/view/e8bmxpkree6nc67/The%20Art%20of%20War.epub
And now, I've tried a couple different tools to try to read this file. Originally, I tried using KFEpubKit. But when I called:
epubURL; // The url from DropBox (shown above)
NSURL *documentsURL = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
self.epubController = [[KFEpubController alloc] initWithEpubURL:epubURL andDestinationFolder:documentsURL];
self.epubController.delegate = self;
[self.epubController openAsynchronous:YES]
I would get back an error that the file couldn't be unzipped. The error reads as:
Epub Error: Error Domain=KFEpubKitErrorDomain Code=1 "Could not extract epub file." UserInfo=0x170275400 {NSLocalizedDescription=Could not extract epub file.}
I looked into the code and narrowed down the problem a little bit. The KFEpubKit uses the SSZipArchive utility to unzip files. And from this point on, I'm a bit stuck. The [SSZipArchive unzipFileAtPath: toDestination:] call seems to be failing when used with the epubURL.path. I'm not sure if this has something to do with the fact that my file is a .epub extension and not a .zip extension. Or maybe there's some stuff to do after getting the URL from DropBox and before giving it to the KFEpubKit tool?
In the end, I'm expecting to have to display the text of the book with a UIWebView. But I'm just not sure how to handle this .epub file. What should I do with the file from Dropbox? Any help is much appreciated.
A quick glance indicates that that SSZipArchive wants a local file URL, not a remote HTTP URL. Try downloading the file first (NSData with contents of URL, then write to some temp file) then create a file URL that points to the temp file, and send that into the KFEpubController:
// Download the file from dropbox
epubURL; // The url from DropBox (shown above)
NSData * epubData = [NSData dataWithContentsOfURL:epubURL];
NSString * tempPath = [NSTemporaryDirectory() stringByAddingPathComponent:#"temp.epub"];
[epubData writeToFile:tempPath atomically:YES];
NSURL *tempURL = [NSURL URLWithString:tempPath];
NSURL *documentsURL = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
self.epubController = [[KFEpubController alloc] initWithEpubURL:tempURL andDestinationFolder:documentsURL];
// etc.
(Coding from memory.) All normal caveats apply here-- you'll want to do proper progress/error handling on the download, get rid of the temp file, etc, etc.

ios 8: Bundle path changes

I have an iOS app that stores the absolute path of files in a database and in generated html documents. I just recently updated my iPhone to iOS 8 and now when ever I run the app it seems that the app is installed in a different directory every re-compile. For example on the first build/run [[NSBundle mainBundle] bundlePath] returns something different on the next build/run. What is going on? Is this a new feature of Apple?
Update: A bug report was created
Code example:
If I run the following line over multiple build/runs then I will get a different result each time.
#define kOLD_PATH #"oldPath"
NSString* newPath = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) lastObject];
NSString* oldPath = [[NSUserDefaults standardUserDefaults] objectForKey:kOLD_PATH];
NSLog(#"New Path: %#", newPath);
NSLog(#"Old Path: %#", oldPath);
NSLog(#"Result: %#", [oldPath isEqualToString:newPath] ? #"Same" : #"Changed");
[[NSUserDefaults standardUserDefaults] setObject:newPath forKey:kOLD_PATH];
[[NSUserDefaults standardUserDefaults] synchronize];
The output looks like this over multiple runs
New Path: /var/mobile/Containers/Data/Application/4FFCE2CB-580D-409A-90CB-EF2B8A1FB653/Library
Old Path: /var/mobile/Containers/Data/Application/B038B2DA-F85D-4E18-A5F1-8635834EC454/Library
Result: Changed
Full Disclosure: In my app the user imports a web page (ePub) that has resources. The resources are stored with the web page. The web page also accesses resources that are part of the app bundle. To achieve this when I load the web page the base url is set to the directory the web page is in and the bundle resources are accessed via absolute file paths. Now that file paths change on every update this is broken. I tried creating symbolic links to the bundle resources but that also fails un subsequent updates.
In iOS 8, The file system layout of app containers has changed. Applications and their content are no longer stored in one root directory.
From the iOS 8 Release Notes:
The file system layout of app containers has changed on disk. Rather
than relying on hard-coded directory structure, use the
NSSearchPathForDirectoriesInDomains function or the
URLForDirectory:inDomain:appropriateForURL:create:error: method of the
NSFileManager class. See Accessing Files and Directories in File
System Programming Guide.
This is not a bug. Make sure you use the recommended APIs (from the above quote) and you won't have a problem.
So, If you are trying to access a bundled resource you added to the project, you would use:
[[NSBundle mainBundle] pathForResource:#"resourceName" ofType:#"extension"];
But if you want to use something that you put in the documents directory, you would use:
[[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject] stringByAppendingPathComponent:#"resourceName.extension"];
Refer Technical Note 2406 by Apple
The breaking change is
Beginning in iOS 8, the Documents and Library directories are no
longer siblings of your application's bundle.
Don't store full path/URL to your documents. Store the file name and always generate full path/URL with recommended approach.
Get the DocumentsDirectory URL
// Returns the URL to the application's Documents directory.
- (NSURL *)applicationDocumentsDirectory {
{
return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
}
Then you get path out of url and append the file name to generate full path.
don't know if you solved your problem, but this link is possible the answer.
https://developer.apple.com/library/prerelease/ios/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/AccessingFilesandDirectories/AccessingFilesandDirectories.html#//apple_ref/doc/uid/TP40010672-CH3-SW10
Locating Files Using Bookmarks
A few lines before this section in the page is this text:
"Important: Although they are safe to use while your app is running, file reference URLs are not safe to store and reuse between launches of your app because a file’s ID may change if the system is rebooted. If you want to store the location of a file persistently between launches of your app, create a bookmark as described in Locating Files Using Bookmarks."
Good bye.
I think the different path for each build and run is the intended way of things happening in iOS simulator. It is not an issue.
/var/mobile/Containers/Data/Application/4FFCE2CB-580D-409A-90CB-EF2B8A1FB653/Library
/var/mobile/Containers/Data/Application/B038B2DA-F85D-4E18-A5F1-8635834EC454/Library
I found even if you use the recommended way
- (NSURL *)applicationDocumentsDirectory
{
return [[[NSFileManager defaultManager] URLsForDirectory:NSLibraryDirectory inDomains:NSUserDomainMask] lastObject];
}
The results are same. Different path for each build & run.

NSURL for a downloaded or otherwise obtained file to open an iOS app with: What is its filepath?

I have found that so far the path sent to application:openURL:sourceApplication:annotation: is:
file://localhost/private/var/mobile/Applications/<GUID>/Documents/Inbox/file
To check that the filesystem operations that I am about to perform are indeed likely to succeed (and that the url given to me is not a location outside the sandbox), it looks like I have to do this:
NSString* hdurl = [[#"file://localhost/private" stringByAppendingString:NSHomeDirectory()] stringByAppendingString: #"/"];
NSString* path = url.absoluteString;
if ([path hasPrefix:hdurl]) {
// now ready to e.g. call fopen on: [path substringFromIndex:#"file://localhost".length]
Now, I seem to vaguely recall (and this is probably wrong) that in the past I have seen the file:/// style URL being used. That would clearly cause this code to fail.
How am I to know that it will always give me a file://localhost URL prefix?
Apple's documentation on URLs is strangely missing a section on file URLs.
An NSURL that points to a file on the local file system is called a "file URL". To convert the NSURL to an NSString representing the file's path you use:
NSString *filePath = [url path];
To check to see if an NSURL represents a file URL, use:
BOOL isFileURL = [url isFileURL];
Keep in mind that if your app is passed a file URL, you will always have access to the file. There is no need to check if it starts with any prefix. Why would iOS pass you a file that you don't have access to?

fileExistsAtPath: returning NO for files that exist

At a point in my code fileExistsAtPath: is returning NO for files that I have confirmed exist. I've been scratching my head at this and can't figure out why its not working, so changed it to this code as this directory absolutely exists but if it doesn't gets created anyway.
NSError* err = nil;
NSURL *dir = [[NSFileManager defaultManager] URLForDirectory:NSApplicationSupportDirectory
inDomain:NSUserDomainMask
appropriateForURL:nil
create: YES
error:&err];
BOOL exists = [[NSFileManager defaultManager] fileExistsAtPath:[dir absoluteString]];
After running this code the application directory folder exists and err is 0, yet exists is NO.
How can this be?
TIA
You should use [dir path], not [dir absoluteString].
I was bashing my head against the wall for a few hours. Apparently on each and every run in xcode (on simulator) the app directory path was changing. The UUID part of it.
So instead of storing the fullpath I ended up persisting the path postfix
and prefixing that with whatever storage class is implied: temporary, cached
or documents :-[ You realize why you have to sometimes run on device
even if you don't explore the depths of Metal, GLES or multitouch? ;^)
There is a distinction between path and url. A path in unix representation is the location where your particular file or directory exists. Like "/Users/username/Desktop/myfile.txt"
Wheres a url not only contains the location but scheme as well, e.g: http:// or https:// and in our case a file url (file:///Users/username/Desktop/myfile.txt)
Methods like fileExistsAtPath or removeItemAtPath need path in the parameter rather than a url.
While methods like copyItemAtURL:toURL:error:(NSError * _Nullable *)error expects a url.
Use path attribute of NSURL object to retrieve the path. absoluteString gives you the path along with the scheme.

Resources