Can't write on plist file after deploying on iPhone - iphone

Dear all.
I am facing a problem.
I can read and write on plist while working on simulator of xcode. but when I deploy the app in iPhone, i can't write on plists.
I have created a sample project having 2 button on it. By one button, I can display the text from plist. By second button, I try to write on that plist. But the writing doesn't happen. The app doesn't crash while clicking on the second button. I can't understand the problem in my code.
/*code is given below*/
-(void)writePlist:(NSString *)fname withArray:(NSMutableArray *) myArray
{
NSString * path = nil;
path = [(NSString *) [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:fname];
[myArray writeToFile:path atomically:NO];
}

It could be that myArray variable contains non-serializable object values (probably custom objects or something like that).

Does the directory you are trying to write to exist? You may want to verify its existence (and create it if it isn't already there) before trying to write a file into it.

Related

Writing values to a .plist more than once?

I have an iPhone App in the works, and I have a settings page within the app. I use a .plist to store the settings that the user picks, and then I read the data from the .plist later. This is not the -Info.plist that comes with it when you create the project, it is another plist file. When I tested the settings after coding it, it worked. The setting was able to be read, and it was the correct setting that I used. However, I went back to the settings in the same app-session, and changed the same setting. When I went to the app to see if it read the new setting, it only used the old (first) one. I tried again, but it yielded the same result. I am only able to write the setting once, and I cannot 'rewrite' or 'overwrite' the same setting. I cannot figure out what is wrong for the life of me.
You can't overwrite or write to files in the app bundle itself. If you want to write to a file, you should first copy it to the app's Library or Documents directory, something like this:
NSString *docsDir = [NSSearchPathsForDirectoriesInDomains(NSDocumentsDirectory, NSUserDomainMask) objectAtIndex:0];
NSString *plistFile = #"myplist.plist";
NSString *plistInBundle = [[NSBundle mainBundle] pathForResource:plistFile ofType:nil];
NSString *plistInDocsDir = [docsDir stringByAppendingPathComponent:plistFile];
[[NSFileManager defaultManager] copyItemAtPath:plistInBundle toPath:plistInDocsDir error:NULL];
// now `plistInDocsDir` is (over)writeable
However, for storing preferences of your app, it's better practice to use the NSUserDefaults class:
[[NSUserDefaults standardUserDefaults] setObject:#"Some string" forKey:#"MySettingKey"];
and/or create a Preference Bundle for your app.

Create documents folder iPhone SDK

I am developing an App that displays several pages of Text when the correct buttons are pressed. It is Static proprietary information. There is a different Text File for each of six buttons.
I am new to the ios SDK. Does creating a project in XCODE automatically create a Documents Folder? Is the "Documents Folder" what Apple is calling the "Sandbox"?
Can I simply write my Text, (that part which will display on the screen, LOTS of Text), drop it into the "Documents Folder", then display it in "scrolling mode" on the iPhone when a certain button is pressed?
I would prefer the Text to be part of the compile, since the information is proprietary, not simply a Text File, if there is a way to store and display large Text Files efficiently.
Yes Ken, the documents directory is there by default when you create an app, and yes, you can certainly write and read text data from it if you want to.
You cannot directly drop data into the Documents folder however, you need to do so programmatically.
Assume that one of your files are 'TextFile1.txt'. You should add this file to your project firstly, and somewhere in the appDelegate, write the following code;
NSString *fileBundlePath = [[NSBundle mainBundle] pathForResource:#"TextFile1" ofType:#"txt"];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *fileDocumentDirectorySavePath = [documentsDirectory stringByAppendingPathComponent:#"TextFile1.txt"];
NSFileManager *fm = [NSFileManager defaultManager];
if (![fm fileExistsAtPath:fileDocumentDirectorySavePath])
[fm copyItemAtPath:fileBundlePath toPath:fileDocumentDirectorySavePath error:nil];
This will copy the TextFile1.txt into your apps Documents folder from which you can read it anytime you need with the following code;
// You can get the fileDocumentDirectorySavePath same way as in the above code
NSString *stringToDisplay = [[NSString alloc] initWithData:[NSData dataWithContentsOfFile:fileDocumentDirectorySavePath] encoding:NSUTF8StringEncoding];
NSLog(#"String : %#", stringToDisplay);
You can do this for any number of text files you need to work with.
If you don't want to change the text dynamically (i.e., only when you submit an update), just add the files directly to your Xcode project and don't even worry about the sandbox/Documents folder. You can drag the files into the sidebar in Xcode (creating a custom folder for it would be very organized of you) and check the "Copy files to project folder?" when asked. As is stated, they're now copied and part of the compiled app. Then you can query the files and display them in a UITextView, automatically supporting scrolling of text.
Alternatively, you could do what I think is the easier method and include the files directly in your code. In a class file that loads the text, in the .h file (Header), add a UITextView as a property and a variable. In the .m file (Implementation), do yourTextView = [[UITextView alloc] init];, then set yourTextView.text to an NSString containing your text. Sounds confusing, but it will be quicker and easier to update in the end. That is, unless your text is formatted... Anyway, you could also just create a UITextView in your XIB/NIB file and add your text directly.
I'd suggest you do it in code. That will be the easiest to change.
Adding text to your app is one thing - making it secure is more difficult. I had to deal with a similar issue and decided to use unformatted text that I include encrypted in my app and only decrpt the part that is being shown. Really depends how "secret" you want to keep the text. Remember, anyone can read it anyway and copy it right from the screen with a screenshot. Also unencrypted text in apps can be read and extracted quite easily using a HexEditor!
Alternatively you can prepare the text in *.txt (unformatted) or html (formatted as you like) file format and just include it in your app. However, this is the easies way for others to just copy the file.

Array does not load data from plist

I'm not sure what exactly is my problem since I have been using this method for a while now and never had this problem.
Basically, as the title says. This code loads data from a plist located in the "Resources" group
NSString *myFile = [[NSBundle mainBundle] pathForResource:#"myList" ofType:#"plist"];
myArray = [[NSArray alloc]initWithContentsOfFile:myFile];
NSLog(#"%#", myArray);
It displays (null). Anyone know why?
I have in the same ViewController another one setup and works just fine. This particular plist does not load however. And I've also even deleted the plist and just created a duplicate from the other one to make sure is not a structure issue.
I am doing this the wrong way? Should I use FileManager?
UPDATE -------------------------------------------------------------------------
Ok so, I can't believe what I am about to say. lol
I am using XCode 4, the way you edit plists in Xcode is different from Xcode 3.x. As in the earlier versions you get to choose the root of the plists to be w/e you want them to (Array, Dictionary, etc) My problem was that the plist I was trying to load into a NSArray was in fact structured as a dictionary. However I never saw this in Xcode, and I have no idea how to see this other than browsing the contents using TextEdit or some other text editor.
Pretty silly I must say. Does anyone know why XCode 4 won't show the root of the plist the way XCode 3.x does?
XCode 3.x: http://dl.dropbox.com/u/919254/XCode3.png
XCode 4: http://dl.dropbox.com/u/919254/Screen%20shot%202011-04-10%20at%202.30.35%20AM.png
Use a dictionary instead. I've had undefined behavior with NSArrays and property lists. e.g.
NSString *myFile = [[NSBundle mainBundle] pathForResource:#"myList" ofType:#"plist"];
NSMutableDictionary* myDict = [[NSMutableDictionary alloc]initWithContentsOfFile:myFile];
NSLog(#"%#", myDict);
I had a similar situation. Solving:
select *.plist in tree
call context menu
select "Open with external editor"
root will be visible - change "Class" to "Array"
save
What you have looks right. Check that the file is in the right directory, and has contents. Might want to NSLog myFile while your at it.

Add String Attribute to CoreDataBooks

I've been messing around with CoreDataBooks and trying to add another field to the sqlite file in addition to the current three. I've added the attribute string in the .xcdatamodel, and declared it in the book.h, book.m, and localized file (all without changing the sqlite file). However, these changes never add the field in sqlite, and the app never loads. Each time I am deleting the app in the simulator and performing a build--> clean, but to no avail. I've also tried changing the sqlite file to match the .xcdatamodel but the app still fails to load.
Is this a problem with CoreDataBooks or me? Do I need to version the app before doing this? Doesn't seem like I should have to as long as I'm deleting the app in the simulator.
Anyone know how I can add this forth string attribute (sqlite field) in CoreDataBooks?
Thanks in advance!
I've found such a nice piece of code in CoreDataBooks
NSString *storePath = [[self applicationDocumentsDirectory] stringByAppendingPathComponent: #"CoreDataBooks.sqlite"];
/*
Set up the store.
For the sake of illustration, provide a pre-populated default store.
*/
NSFileManager *fileManager = [NSFileManager defaultManager];
// If the expected store doesn't exist, copy the default store.
if (![fileManager fileExistsAtPath:storePath]) {
NSString *defaultStorePath = [[NSBundle mainBundle] pathForResource:#"CoreDataBooks" ofType:#"sqlite"];
if (defaultStorePath) {
[fileManager copyItemAtPath:defaultStorePath toPath:storePath error:NULL];
}
}
in CoreDataBooksAppDelegate.m:154, this means for me that every time apps does not find sqlite file it is copied from bundle where it was not changed by modification of CoreDataBooks.xcdatamodel.
Try to use another approach, or just modify bundled sqlite file.
If the app doesn't load then there might be something wrong with your datamodel. Check the console for error messages.
And you have to decide if you want to use sqlite or core-data. If you want to monitor sqlite files for changes or add fields to sqlite files you should use sqlite and not core-data.
If you want to use core-data you should ignore the sqlite file.
Figured it out. The application WAS creating the new sqlite file, I just needed to find it within the simulator files. Grrrr, so much time wasted when I was doing everything correctly. Lessons learned I guess :)

Link to pdf within app

I've got an iOS app that at one point opens a link to a website in a webview. These links are kept in a plist file (so it is easy to maintain them as the app evolves). What I want to do next is to also link to PDF's (or any picture of text file format, of even a html format, this is flexible) that are kept within the app. And I would like to do this as much as possible from within the existing app structure. So, is it possible to create a link that can be put in the plist as a web-link, but instead opens a file on the device itself (possibly in the webview)? And how would I go about that? Any ideas?
Thanx in advance for your help!
You will need to create the links at runtime. I would suggest having a certain prefix to a local url, such as mylocalfile:filename. Then, in the code that loads the plist, check for the prefix and create the link when necessary. You could also just create these links once and store them in a separate file, then load that instead of the original.
NSArray *links = nil; //I assumed your plist is an array. Change to dictionary if required
NSString *pathToStoredFile; //Get the path for the file you create with the updated links
if([[NSFileManager defaultManager] fileExistsAtPath:pathToStoredFile]) {
links = [NSArray arrayWithContentsOfFile:pathToStoredFile];
} else {
NSArray *tmp = [NSArray arrayWithContentsOfFile:[[NSBundle mainBundle] pathForResource:#"ListOfLinks" ofType:#"plist"]];
if(!tmp) {
//handle error
}
NSMutableArray *tmpLinks = [[NSMutableArray alloc] initWithCapacity:[tmp count]];
for(NSString *link in tmp) {
if([link hasPrefix:#"mylocalfile:"]) {
link = [link substringFromIndex:12]; //12 is the length of mylocalfile:
NSURL *url = [[NSBundle mainBundle] urlForResource:[link stringByDeletingPathExtension] withExtension:[link pathExtension]];
[tmpLinks addObject:[url absoluteString]];
} else [tmpLinks addObject:link];
}
links = [tmpLinks copy];
[tmpLinks release];
[links writeToFile:pathToStoredFile atomically:NO];
}
Yes, I would go with a UIWebView. iOS should be able automatically handle certain URL handlers and your app can register to handle the rest, as necessary.
iOS knows how to handle certain file types already. For example, if Safari (or a UIWebView) encounters http://somesite.com/afile.pdf, it know which apps can handle the file type. Another example is a phone number: skype://8005555555. iOS knows to open Skype and pass the number to it. iOS also knows that iBooks can handle PDf files.
Register your app for the appropriate file handlers and types. Then, users can tap and hold on the link to see a menu of available apps to handle the link. If it's a link that's only used by one app, the user doesn't even need to hold, a tap will suffice.
As far as making a link pointing to a local file, you can, and you would use the C function NSDocumentsDirectory() and append that to a url handler. (Example: http://NSDocumentsDirectory()/filename.pdf)