I'm making an app that uses encoding to store objects on the document directory,
On iOS Simulator, the objects are getting saved perfectly, and if i closed the app and got back to it all the data are restored with no issue.
But today i tried my app on the iPhone 5s, the objects are not getting saved when i close the app and go back to it again all the data are getting removed only on the real device, what is the problem ?
I'm using this method to get the directory path:
- (NSString *)pathForTask
{
NSArray *documentDirectories = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentDirectory = [documentDirectories firstObject];
return [documentDirectory stringByAppendingString:#"Tasks"];
}
Archive:
NSString *path = [self pathForTask];
[NSKeyedArchiver archiveRootObject:self.privateTasks toFile:path];
Unarchive:
NSString *path = [self pathForTask];
_privateTasks = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
The following line of code is causing your problem:
return [documentDirectory stringByAppendingString:#"Tasks"];
The results in the returned path being something like:
.../DocumentsTasks
Note the lack of a slash.
Change the line to:
return [documentDirectory stringByAppendingPathComponent:#"Tasks"];
This will return a proper path and your code will work on a device.
It works in the simulator because you end up creating a file named DocumentTasks in some folder on your computer. But on a real device, the folder that this file is trying to be written to is read-only due to sandboxing.
Try getting the documents directory using the following code instead:
- (NSURL *)applicationDocumentsDirectory
{
return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
}
After getting the documents directory, you can get the path to a specific file by doing:
NSURL *fileURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"[FILE NAME]"];
I'm not sure why your code doesn't work, but this is the code I use on my app and it works in the simulator and the physical device.
Frequently code that works on the sim but not on the device is caused by filename upper/lower case mismatching.
The file system in Mac OS is almost always case-insenstive. (You can use disk volumes who's file system are case sensitive in Mac OS, but they are not by default.)
The simulator runs on Mac OS. It's file system is not case sensitive. So if you save a file as "Tasks" and then load it as "tasks" it works on the sim, but not on an actual iOS device. Make sure you check really carefully for mismatched case in your filenames.
Related
i deployed my App to my iPhone and get
Unknown error calling sqlite3_step (8: attempt to write a readonly database) eu
on Insert / Update Statements.
On the Simulator it all works like it should.
My sqlite Database is placed in the Resource Folder (Xcode).
Thanks for help!
Your application bundle is not writable on the iPhone. You MUST copy the file somewhere else, like your documents folder. It works in the simulator because the Mac does not enforce all the sandboxing restrictions the iPhone does.
You can copy your database from the application bundle directory to the Documents directory in viewDidLoad. You can read/write from/to your database in the Documents directory after this. Of course, you need to check if the database in the Documents directory exist before you do the copy in order not to overwrite it the next time you bring up the app.
Assuming you have defined your database name '#define kFilename #"yourdatabase.db"' in the .m file.
In viewDidLoad add:
// Get the path to the main bundle resource directory.
NSString *pathsToReources = [[NSBundle mainBundle] resourcePath];
NSString *yourOriginalDatabasePath = [pathsToResources stringByAppendingPathComponent:kFilename];
// Create the path to the database in the Documents directory.
NSArray *pathsToDocuments = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [pathsToDocuments objectAtIndex:0];
NSString *yourNewDatabasePath = [documentsDirectory stringByAppendingPathComponent:kFilename];
if (![[NSFileManager defaultManager] isReadableFileAtPath:yourNewDatabasePath]) {
if ([[NSFileManager defaultManager] copyItemAtPath:yourOriginalDatabasePath toPath:yourNewDatabasePath error:NULL] != YES)
NSAssert2(0, #"Fail to copy database from %# to %#", yourOriginalDatabasePath, yourNewDatabasePath);
}
Good luck!
aobs
I am saving audio data to the Documents directory and trying to read it back. If I play it back immediately it plays successfully, however, if I start a new session and try and play the song locally it will fail even though listing the files in the Documents directory shows that my file is still there. Note that the file is played back from the Documents folder in the same way (same code) if it is played immediately or during a new session.
Here is how I save the audio data to the Documents directory:
+(void)writeDataToAudioFile:(NSData*)data forTrack:(MediaItem*)track
{
// filename looks like "[track_id].mp3"
NSString *fileName = [NSString stringWithFormat:#"%#.%#",track.sc_id,track.original_format];
NSString *pathName = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
NSUserDomainMask,
YES) firstObject]
stringByAppendingPathComponent:fileName];
[[NSFileManager defaultManager] createFileAtPath:pathName
contents:data
attributes:nil];
}
Then in my music player I want to load the local URL to this file to initialize the AVPlayer:
NSURL *url;
if(_currentTrack.is_local_item)
{
url = [NSURL fileURLWithPath:_currentTrack.local_file_path];
}
url does not get created properly as AVPlayer does not play. Furthermore, I have tried every different way to load the file as data into an NSData object to check the byte size but trying to access the file as data always returns nil. However, the file exists as if I use NSFileManager I am able to iterate over the items in the Documents directory and print their file names/paths, validating that I the path I have saved in "_currentTrack.local_file_path" does exist. Again, if I play the file immediately after saving the file to disk it will play back.
If there is more info I can provide to make this clearer I will. Thank you very much.
Do not write the full directory path to DB. It will change. You need to only save the file name to DB as reference. Then use as follows:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsPath = [paths objectAtIndex:0];
NSString *fileName = #"SAVED_FILE_NAME.mp3"; // eg: [track_id].mp3
NSString *filePath = [documentsPath stringByAppendingPathComponent:fileName];
This will provide you the actual path of the file.
Keep coding........... :)
I found the solution after putting the problem down for a few days. I break-pointed and print-stated the heck out of the program and I found that the file path I was saving was not the same as the file path of the file.
I think this was a simulator issue, as the issue only occurred between different sessions of the simulator, and worked within the same session, so the device id (which is part of the absolute path) was changing - maybe someone more knowledgeable can weigh in on that.
Pay closer attention to the string values of your variables folks!
My app was working perfectly in iOS 7, and now I'm facing some new bugs after switching to iOS 8 SDK.
For Ex. [NSFileManager defaultManager] file existence checking method ( fileExistsAtPath: ) is no longer working. Here's the details about current situation in my code:
This is the block of code whose condition never turns True :
File *tempFile = currentMessage.contains;
NSString *address = tempFile.thumbAddress;
if ([[NSFileManager defaultManager] fileExistsAtPath:currentMessage.contains.thumbAddress])
{
image = [UIImage imageWithContentsOfFile:currentMessage.contains.thumbAddress];
}
I have put a breakpoint before the if and traced it to see what is contains :
Printing description of address:
/Users/Unkn0wn/Library/Developer/CoreSimulator/Devices/C07E1D76-372E-4C9A-8749-50D369294FBA/data/Containers/Data/Application/1DC7DB3A-B0A2-43AE-9EDA-3E121786D1AA/Documents/FileAt141406917489510096thumbnail.jpg
I have checked the Finder to see if the file actually exists ( the address is copied from console so there's no chance of mistyping and typos in checking the path and file existence ) :
But that if block is not executed because fileExistsAtPath: returns NO.
Am I missing anything or it is just a bug of new SDK ? ( Because it was working perfectly with SDK 7.1.1 )
Any suggestions is highly appreciated.
Thanks
If thumbAddress is a full path (not a relative one), it can cause bugs when the app is updated because the app folder changes, for example, your first version of the app could be at that path:
/Users/Unkn0wn/Library/Developer/.../Application/ABCD/
When you update the app, the directory will change, for example:
/Users/Unkn0wn/Library/Developer/.../Application/EFGH/
So if you save a full path for your image in the first version of the app like :
ABCD/yourImage.jpg
And try to retrieve this image with the new version of the app, this image will not be found anymore as your image will now be at the path:
EFGH/yourImage.jpg
Hopes it helps!
EDIT
Maybe your absolute path is incorrect. Try calling it with a relative path:
NSString *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
path = [path stringByAppendingString:#"FileAt141406917489510096thumbnail.jpg"];
NSData *imageData = [NSData dataWithContentsOfFile:path]; // Should be != nil
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.
I am having problems updating an app to iOS7 SDK. Before I've used iOS 6 SDK and accessed my mp3 file using a NSURL for the folder like this:
NSURL *folderURL = [[NSBundle mainBundle] URLForResource:#"" withExtension:#"" subdirectory:#"AudioGuide"];
Now, using the iOS 7 SDK I always get nil as the value for folderURL and my audio guide doesn't find the mp3's anymore.
I've already looked into the generated .app-Bundle for the simulator (in ~/Library/Application Support/...), and I can see the "AudioGuide" folder in the root. So it's definitly there.
I am not that iOS guru and didn't really follow iOS7 updates. Has there been any changes made on how to access own assets in an app? How do I access my files?
Apple doc for - (NSURL *)URLForResource:(NSString *)name withExtension:(NSString *)extension subdirectory:(NSString *)subpath
Returns the file URL for the resource file identified by the specified
name and extension and residing in a given bundle directory.
what that means is you can get the file URL with specific type in a given subdirectory, so you should use this method call with your file name and extension.
If you are trying to read the list of files under that directory, what you can do is bundle all of your audio files in a single zip or gzipped file and you extract it in your Documents or Application Support directory when it is accessed for the very first time, then you can read it using the code below.
NSArray *directories = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES);
NSString *directory = [directories objectAtIndex:0];
NSString *audioDirectoryPath = [directory stringByAppendingPathComponent:#"AudioGuide"];
NSArray *audioFiles = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:audioDirectoryPath];
Hope this helps!