<newbie> Objective C, saving a file in iPad apps - objective-c

I am creating a sample application where it downloads 100 images from
server and stores it in iPad.
In XCode, I am using the NSURL to retrieve the image file and using
NSData to save it into my local folder.
I am able to save the images in my mac air desktop folder. But I want
my application to be deployed in iPad.
So here is my question:
Where do I store the file in my iPad, so that my application can
retrieve the images when needed?
If possible can someone give me the code for saving the images in
your iPad resource directory (or whatever directory needed). Just the
code where you build the path will do good.
I know this is kind of a basic question, since I am new to objective-C, I am kind of struggling with it.

Here is an example function how to retrieve the applications documents folder. In there you can create your own folder structure. This folder is also backed up by iTunes and will be preserved when doing application updates.
NSString* GetApplicationDocumentsDirectory() {
static NSString* documentsDirectory = nil;
if (documentsDirectory == nil) {
documentsDirectory = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
NSUserDomainMask,
YES)
objectAtIndex:0] retain];
}
return documentsDirectory;
}

In my humble opinion, using a Core Data store would be the best approach for this.

Related

Objective C reading array from file fails on different locations

I am developing a mac app and I want to put my settings (array of strings) into a file. If that file exists in the same folder as the app it is read and it overwrites default settings.
No problems writing the file but when reading with code:
NSArray* settingsInput = [[NSArray alloc] initWithContentsOfFile:#"./SettingsFile"];
something strange happens. When running the app from XCode (the settings file is in the build/debug folder next to the app) settings are read without a problem. When I archive the app and run it from the desktop, the file cannot be loaded. Even when I copy the app from the build folder to the desktop it does not work.
What could be the reason for this kind of behaviour? How can I read this file?
It may be a better Idea to use the normal prefence system. NSUserDefaults.
There a couple of ways you can do it.
But the idea is to give your app a set of default preference which are registered for you in the correct domain and always with a fresh app.
Using the registerDefaults: from NSUserDefaults.
See Apples documentation NSUserDefaults and its #registerDefaults
But the one I would use is :
Copy a plist file into the supporting files in you Xcode project.
Making sure "Copy files into destination group's folder" is checked. And the "Add to targets is check also"
The plist file should contain your array of strings.
(I created mine. By duplicating another plist in my user preferences. Renaming it. Copying it to the project. Selecting it and editing it to how I needed it. Making sure I use the file menu ->'Save' to save the changes. )
Declare a NSUserDefaults * prefs;
Now in the - (id)init method for the app. you register the contents of the file as your default preferences.
- (id)init
{
self = [super init];
if (self) {
prefs = [NSUserDefaults standardUserDefaults] ;
NSString *registerDefaultsPlistFile= [[NSBundle mainBundle] pathForResource:#"registerDefaults" ofType:#"plist"];
[prefs registerDefaults:[NSDictionary dictionaryWithContentsOfFile: registerDefaultsPlistFile]];
}
return self;
}
You can later make a call to read these preferences.
NSLog(#" arrayOfStrings = %#", [prefs objectForKey:#"arrayOfStrings" ]);
These default preferences are NOT written to file/out unless you make a change to them. By written to file I mean to the applications preference file. Once you do make a change to them then they will be written out into the users preferences and those are what will be used from then on.
You should not rely on the current directory of the app. You should either read from the app bundle (see NSBundle class for get the correct path) or the app's document directory (see NSSearchPathForDirectoriesInDomains(NSDocumentDirectory ...).
The UNIX concept of the current working directory is not commonly used in Mac desktop applications. When launching an app through the Finder it's usually set to the root directory of the boot volume.
You should find another way to determine locations for your settings files. Good spots would be ~/Library/Preferences or ~/Library/Application Support/MyApp. You can get the absolute path to these directories using:
NSString *path = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomain, YES)[0];

Save in plist to a program directory

i need to save a .plist file NOT to documents, but to the core of program.
For example my program called "123" and if i save data, then send my app to my friend and he opens this app he could see saved data, no matter where he puts this program. I can't find solution to this problem, please help me.
I'm making mac app.
and i save plist with
[NSKeyedArchiver archiveRootObject:FBCover1.text=
[NSString stringWithFormat:#"%#",Cover1.attributedStringValue]
toFile:#"/Users/admin/FBCover1.plist"];
General answer:
If you're trying to do this on iPhone (you didn't tag this for iOS or MacOS), this isn't going to work as this will break your code signing.
If you're doing this on MacOS and you're using code signing, you'll have the same problem.
There may be places where you could save and share data, such as Game Center or DropBox or Box or some other cloud storage mechanism, but you'll need to pick up and make use of some additional API's or frameworks.
Specific answer just for you:
Instead of:
[NSKeyedArchiver archiveRootObject:FBCover1.text=[NSString stringWithFormat:#"%#",Cover1.attributedStringValue] toFile:#"/Users/admin/FBCover1.plist"];
which is big and ugly and I don't know what the heck it's doing, why not save your string this way?
NSString * stringToSave = [NSString stringWithFormat:#"%#",Cover1.attributedStringValue];
if(stringToSave)
{
NSError * error = nil;
BOOL success = [stringToSave writeToFile: #"/Users/admin/FBCovert1.txt" atomically: YES encoding: NSUTF8StringEncoding error: #error];
if(!success)
{
NSLog( #"error in saving - %#", [error localizedDescription]);
}
}
This saves the raw string into a file.
If you want to do it as a plist, then create a NSDictionary and save your string as the value with some appropriate key.
Preamble: this is an awful idea. What you should do is create a document-based application and pass your document backwards and forwards.
Literal answer:
You can use NSBundle to get the path of the resources folder within your application bundle with something like:
[[NSBundle mainBundle] resourcePath]
The resources folder is where application resources, such as plists, are meant to go. You're supposed to consider your application bundle as read-only in general but that's as good a choice as any if you want to hack away.

copying file from temp directory to resource folder

I am able to record a .caf file in my app and play it back to the user. I want to be able save it permanently so I want to move it to my resources folder. I am not sure if this is the correct move or whether it would be better to put it in the documents directory but I access all my other sound files from resource so I assumed this would be the right choice. Does anyone know how to do this? Thanks!
How I save the recording to temp:
NSURL *soundFileURL=[NSURL fileURLWithPath:
[NSTemporaryDirectory()
stringByAppendingString:#"soundFile1.caf"]];
You cannot add, remove or modify files in your application bundle, which means you can't add items to the resources directory.
If the file is something the user will want to keep in the app, it should be stored in the documents directory. You may want to prevent it being backed up to iCloud, which you can do by following Apple's instructions here.
To get a path to the documents directory, or the path for a given filename in the documents directory, you can use methods like these:
+ (NSString *)pathForDocumentsDirectory
{
NSArray *documentPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
return [documentPaths objectAtIndex:0];
}
+ (NSString *)pathForFileInDocumentsDirectory:(NSString *)filename
{
return [[self pathForDocumentsDirectory] stringByAppendingPathComponent:filename];
}
You cannot add anything into your application bundle of your application while using application so add them in document or library folder of application

Are iCloud images being cached in iOS device after being retrieved by application?

I have a simple UITableView, where each cell has an thumbnail picture that the user may have taken with its iOS device camera.
If iCloud is enabled the image is saved in it. However I was wondering if some sort of caching happens when loading the image, because I have notice a slowness on first loading then, even if the piece of code is called again when cell display on screen, the image show quite fast.
This is the relevant code fragment, I have omitted the logic for building the cell, I think it is not relevant, because the question is about other aspects:
- (UITableViewCell *)tableView:(UITableView *)table cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// logic for retrieve data and build cell
NSURL *ubiquityUrl = [fm URLForUbiquityContainerIdentifier:nil];
NSURL *docURL = [ubiquityUrl
URLByAppendingPathComponent:[NSString stringWithFormat:#"P_%#_%#.jpg",imgId,#"thumbnail"]
isDirectory:NO];
// this a custom object extending UIDocument
IP2DataDocument *dataDocument = [[IP2DataDocument alloc] initWithFileURL:docURL];
[dataDocument openWithCompletionHandler:^(BOOL success) {
if (success) {
NSLog(#"iCloud document opened");
// logic for filling table cell picture
} else {
NSLog(#"failed opening document from iCloud");
}
}];
// returning cell
}
I can see "iCloud document opened" in Xcode, each time the cell display.
If some sort of caching occurs, can you point out where and how ?
The Document-based App Programming Guide for iOS says:
When you run a metadata query to learn about an application’s iCloud documents, the query results are placeholder items (NSMetadataItem objects) for document files. The items contain metadata about the file, such as its URL and its modification date. The document file is not in the iCloud container directory.
The actual data for a document is not downloaded until one of the following happens:
Your application attempts to open or access the file, such as by calling openWithCompletionHandler:.
Your application calls the NSFileManager method startDownloadingUbiquitousItemAtURL:error: to download the data explicitly.
To sum it up: The first time you open your document, it may not have been downloaded from iCloud yet, which is why it'll take longer. Afterwards, there is a local copy of the file that is obviously faster to read.
It is your docURL or specially
NSURL *ubiquityUrl = [fm URLForUbiquityContainerIdentifier:nil];
You don't actual access directly to iCloud. You access an equivalent local location (ubiquityUrl) that is iCloud enabled. If the file that you try to get has not been downloaded locally yet, then the local iCloud daemon will download it at the time you are accessing. Once this occurs, it is stored in your device locally. So the next time, you try to access it again using the docURL, it is much quicker because of this. Also the iCloud daemon is responsible for syncing version your document. So you don't need to worry about that if someone else has updated a newer version of the same document from another device.

Is it possible to copy a different plist into the application bundle code wise?

My question is stated above in the title but I'll write it here again
Is it possible to copy a different plist into the application bundle code wise??
This is actually a bit related on my previous question ;) Create a dictionary property list programmatically
But since I can write out the plist (and test if I'm doing something wrong ) I was wondering if I can copy my written file
[testBook writeTofile:#/Users/jack/testbook2.plist" atomically:NO];
to my application bundle in code (so that it can read from here with
NSString *path = [[NSBundle mainBundle] bundlePath];
NSString *dataPath = [path stringbyAppendingPathcomponent:#"testbook2.plist"];
Another alternative is good as well for instance if I can read my plist directly from another path instead of the mainBundle
Physically, yes it is possible sometimes.
NO, DON'T DO THAT!!
Okay... copying stuff into your App Bundle is bad, your app bundle is not where you keep settings or data. You should think of your app bundle as readonly. You should do this for several reasons:
While most of the time a user on a Mac OS X system has permission to write to an app sometimes they don't. Think about children who are not using admin accounts, or lab deployments at schools.
It will invalidate codesigning. If you sign your application this will change the signature and break the codesign, causing all sorts of weird issues (losing any firewall permissions the app has, telling the user the app has changed and asking them for keychain permission again, etc). In the future this may also pop up all sorts of warnings since tampering with an app is a big security red flag.
It won't work on iPhone. That may not be an issue, but if you every intend to use this code on an iPhone the apps bundles are sandboxed readonly
The correct place to put this sort of stuff is into a sub folder of the Application Support folder
I don't know exactly what you're doing, but depending on your application, you might want to use the Application Support directory:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES);
if ([paths count] > 0) {
NSString *path = [[paths objectAtIndex:0] stringByAppendingPathComponent:#"App Name"];
// Create the directory if it doesn't exist, etc.
} else {
NSLog(#"Fail!");
// ...
}