I know this question has been asked a hundred times here because I have been reading most of the questions about reading and writing to plists in this forum, in fact this is the main reason why I'm posting this question. I'm tired of trying outdated tutorials about this topic. I have tried a few tutorials but most of them are using .xib files or are not using ARC and I usually end up with a bunch of errors.
Does anyone know about a tutorial about reading/writing to a plist that uses storyboards and ARC? In other words an updated tutorial.
All I need is a tutorial that I can reference to have a better understanding on how to persist data using plists.
Thanks a lot
Here is a very simple piece of code that shows how to read and write to a plist. Code is based on this tutorial.
What I have here is basically two labels and two buttons on screen, one button to write the data and the other one is to read the data when clicked, the two labels are to show the first two items from an array (plist), item 0 will be shown in the first label and item 1 will be shown in the the second label.
.m file
-(NSString *)getFilePath
{
NSArray *pathArray = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
return [[pathArray objectAtIndex:0] stringByAppendingPathComponent:#"fruitList.plist"];
}
-(void)saveData
{
NSArray *value = [[NSArray alloc] initWithObjects: #"Orange", #"Apple", nil];
[value writeToFile:[self getFilePath] atomically:YES];
}
-(void)loadData
{
NSString *myPath = [self getFilePath];
BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:myPath];
if(fileExists)
{
NSArray *values = [[NSArray alloc]initWithContentsOfFile:myPath];
self.labelOne.text = [values objectAtIndex:0];
self.labelTwo.text = [values objectAtIndex:1];
}
}
- (IBAction)writeData:(id)sender
{
[self saveData];
}
- (IBAction)readData:(id)sender
{
[self loadData];
}
.h file
#property (weak, nonatomic) IBOutlet UILabel *labelOne;
#property (weak, nonatomic) IBOutlet UILabel *labelTwo;
-(NSString*) getFilePath;
-(void) saveData;
-(void) loadData;
- (IBAction)writeData:(id)sender;
- (IBAction)readData:(id)sender;
Related
I am very new to creating applications and haven't fully figured out how to use the plist function within XCode.
My problem is that I have 3 different input methods within a view controller to which the user will select values from, those being a stepper, a picker view and a date that logs the current date, which I would like to save to a plist so that the user can view those entries in a table view within another view controller.
I haven't really used a plist before therefore my question may sound very silly but regardless I need some help with this.
So far I have the inputs setup but they don't really do anything, I know this question is very basic but I am struggling to find information on this that doesn't go too technical.
I can post my code if that will be beneficial.
Any help will be greatly appreciated.
#property (weak, nonatomic) IBOutlet UILabel *balesFedLabel;
#property (weak, nonatomic) IBOutlet UIStepper *balesFedStepper;
#property (weak, nonatomic) IBOutlet UIPickerView *fieldPickerView;
#property (weak, nonatomic) IBOutlet UILabel *dateLabel;
#property (weak, nonatomic) IBOutlet UITextField *sheepGroup;
#property (strong, nonatomic) IBOutlet UIBarButtonItem *backButton;
//Actions
- (IBAction)stepperValueChange:(id)sender;
- (IBAction)saveButton:(id)sender;
- (IBAction)textFieldDoneEditing:(id)sender;
#property NSArray *dataSource;
#property NSString *tempFieldSelection;
#property(nonatomic) UIKeyboardAppearance keyboardAppearanceDark;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self setupArray];
NSLocale *gbLocale = [[NSLocale alloc] initWithLocaleIdentifier:#"en_GB"];
NSString *dateFormatString = [NSDateFormatter dateFormatFromTemplate:#"dd/MM/yyyy" options:0 locale:gbLocale];
NSLog(#"dataFormatString: %#", dateFormatString);
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:dateFormatString];
NSString *stringFromDate = [dateFormatter stringFromDate:[NSDate date]];
self.dateLabel.text = stringFromDate;
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {[self.view endEditing:YES];
}
- (void)setupArray {
_dataSource = [[NSArray alloc] initWithObjects:#"Cow Pasture", #"Top Lot", #"East Lot", #"West Lot", #"Front Meadow", #"Big Meadow", nil];
}
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView {
return 1;
}
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component {
return [_dataSource count];
}
- (UIView *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component {
return [_dataSource objectAtIndex:row];
}
- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component {
self.tempFieldSelection = [_dataSource objectAtIndex:[pickerView selectedRowInComponent:0]];
}
- (IBAction)stepperValueChange:(id)sender {double baleStepperValue = self.balesFedStepper.value;
self.balesFedLabel.text = [NSString stringWithFormat:#"%.f", baleStepperValue];
}
- (IBAction)textFieldDoneEditing:(id)sender {[sender resignFirstResponder];
}
- (IBAction)saveButton:(id)sender {
NSLog(#"Bales Fed: %#", self.balesFedLabel.text);
NSLog(#"Sheep Group: %#", self.sheepGroup.text);
NSLog(#"Current Field: %#", self.tempFieldSelection);
NSLog(#"Last Date Fed: %#", self.dateLabel.text);
}
Use an NSMutableDictionary and pass it to the destination UIViewController in a property.
For example in your source view controller:
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
dict[#"Bales Fed"] = self.balesFedLabel.text;
dict[#"Sheep Group"] = self.sheepGroup.text;
dict[#"Current Field"] = self.tempFieldSelection;
dict[#"Last Date Fed"] = self.dateLabel.text;
Just pass dict to the destination view controller.
If you want to use a plist these are the two methods available in a NSDictionary class:
- writeToFile:atomically: to write the dictionary to a file (in plist format)
and the class method dictionaryWithContentsOfFile: or the instance method initWithContentsOfFile: to retrieve the dictionary from disk.
In your case, to write the dictionary to a file in plist format:
NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
NSString *filePath = [documentsPath stringByAppendingPathComponent:#"myData.plist"];
[dict writeToFile:filePath atomically:NO];
And in the destination view controller use this code to retrieve the plist from disk:
NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
NSString *filePath = [documentsPath stringByAppendingPathComponent:#"myData.plist"];
NSDictionary *myData = [NSDictionary dictionaryWithContentsOfFile:filePath];
From Apple's documentation for the first method:
This method recursively validates that all the contained objects are property list objects (instances of NSData, NSDate, NSNumber, NSString, NSArray, or NSDictionary) before writing out the file, and returns NO if all the objects are not property list objects, since the resultant file would not be a valid property list.
If the dictionary’s contents are all property list objects, the file written by this method can be used to initialize a new dictionary with the class method dictionaryWithContentsOfFile: or the instance method initWithContentsOfFile:.
I also recommend you to read the guide Property List Programming Guide
Even when i wouldn`t recommend to use a .plist file here is the code to use it:
NSString* plistPath = [[NSBundle mainBundle] pathForResource:#"YOUR_FILE_NAME" ofType:#"plist"];
NSDictionary *plistDic = [NSDictionary dictionaryWithContentsOfFile:plistPath];
As you are just passing small amounts of user entered data from one view controller to another you do not need to use PLists nor CoreData nor NSUserDefaults. These are all suitable for persistent data but from your description it sounds like it is just transient stuff.
Just store the user data in parameters and then pass them forward to the destination ViewController using the prepareForSegue method. See this SO answer for full details.
When my app loads up it builds a menu using a UITableViewController, this menu is split in two parts. The first part has 4 items from a 'hard-coded' array, the second part is made dynamically from a list of all text files in the documents folder.
When my app runs it builds the menu and then gets files if they're needed (which the menu needs to be complete). So the second part of my menu is blank. If I run the app again the menu is fine as the files now exist.
What I want is, when the file has finished downloading, for the UITableViewController to be reloaded and so the menu rebuilt.
The number of files can and will change and will be updated often with a timestamp check carried out just before the file is downloaded.
Where the file is downloaded:
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
MenuViewController *refreshedMenu = [[MenuViewController alloc]init];
[refreshedMenu reloadTableView];
// Have also tried:
// [refreshedMenu performSelectorOnMainThread:#selector(reloadTableView)withObject:nil waitUntilDone:NO ];
[file closeFile];
}
The UITableViewController's code:
-(void)reloadTableView{
NSLog(#"reloadTableView has been run");
[self buildMenuArrays];
[self.tableView reloadData];
}
-(void)buildMenuArrays{
self.mainMenu = [NSMutableArray arrayWithObjects:#"Home", #"Exhibitor", #"Speaker", #"Workshop", nil];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *docDirectory = [paths objectAtIndex:0];
NSFileManager *fileManager = [[NSFileManager alloc] init];
NSArray *files = [fileManager contentsOfDirectoryAtPath:docDirectory error:nil];
self.infoMenu = [[NSMutableArray alloc] init];
for (int count = 0; count < files.count; count++) {
NSString *currentFile = [files objectAtIndex:count];
if ([currentFile rangeOfString:#".txt"].location == NSNotFound) {
NSLog(#"File %# does not contain .txt", currentFile);
} else {
NSLog(#"File %# DOES contain .txt", currentFile);
[self.infoMenu addObject:[files objectAtIndex:count]];
}
}
NSLog(#"File array = %#", self.infoMenu);
}
At the moment, the array is updated and the correct file names are there but [self.tableView reloadData]; seems to do nothing.
Any ideas?
EDIT
This seems like such a simple process but I see many other people have the same problem. IS the approach I'm taking fundamentally wrong? It seems like calling a UITableViewController's reloadData method should be very easy to do, but isn't?
You seem to be making a new object of MenuViewController in connectionDidFinishLoading method. This will give you a new object allright but where have you added the view of this new TableViewController object into the existing ViewController?
You need to add view of this newly created TableViewController object onto your view hierarchy.
This question already has answers here:
NSUserDefault and Switches
(2 answers)
Closed 9 years ago.
I would like to ask about uiswitch in uitableview custom cell.
if I have array that keeps state of each uiswitch in the table and update it on Change Value Event.However this changes are not persistence each time i open the app it reset to it's initial state. My question is how to make the changes persistent on my app whenever i close it and reopen it again ?
Here is my change value code:
-(void)switchChanged:(UISwitch *)sender
{
UITableViewCell *cell = (UITableViewCell *)[sender superview];
NSIndexPath *x=[mainTableView indexPathForCell:cell];
NSMutableArray *repl = [[NSMutableArray alloc] init];
if (sender.on)
{
repl= [SwitchArray objectAtIndex:x.section] ;
[repl replaceObjectAtIndex:x.row withObject:#"ON"];
}
else
{
//call the first array by section
repl= [SwitchArray objectAtIndex:x.section] ;
[repl replaceObjectAtIndex:x.row withObject:#"OFF"];
}
}
Here is the initial values of the array in viewDidLoad:
for(int j=0 ; j < 30 ; j++)
[switchArray addObject:#"ON"];
Thanks in advance. I appreciate your collaboration This will make me happy
One easy way to persist the array between uses of the app is to write out the array to a pList.
In order to do so you'll need a place to store the file, take the following example:
- (NSURL *)switchArrayFilePath {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSURL *filePath = [NSURL fileURLWithPath:[documentsDirectory stringByAppendingPathComponent:#"SwitchArray.plist"]];
return filePath;
}
Then in order to load up your array, you can read the pList back in in viewDidLoad: for instance:
self.switchArray = [[NSMutableArray alloc] initWithContentsOfURL:[self switchArrayFilePath]];
Then in order to write out the data, you could so something like this in viewWillDisappear:
[self.switchArray writeToURL:[self switchArrayFilePath] atomically:YES];
Other ways to persist this type of data on iOS would be use NSUserDefaults or use Core Data but that would be complex for something simple like this.
Hope that helps!
In my app I am downloading images from url and saving into application documents directory. Now I want to show these downloaded images as image gallery. For image gallery I am using fGallery, I am able to configure it and successfully pushed it on navigation controller and fGallery view is also visible but its not showing my images, I provide fGalley with images array like this
Header File
#interface myController <FGalleryViewControllerDelegate>
#property (strong, nonatomic) FGalleryViewController *localGallery;
#property (strong, nonatomic) NSMutableArray *localImages;
Class File
//These are delegate methods of FGalleryViewControllerDelegate
- (NSString*)photoGallery:(FGalleryViewController*)gallery filePathForPhotoSize:(FGalleryPhotoSize)size atIndex:(NSUInteger)index
{
return [localImages objectAtIndex:index];
}
- (NSString*)photoGallery:(FGalleryViewController *)gallery urlForPhotoSize:(FGalleryPhotoSize)size atIndex:(NSUInteger)index
{
return [localImages objectAtIndex:index];
}
- (int)numberOfPhotosForPhotoGallery:(FGalleryViewController *)gallery
{
int num;
num = [localImages count];
return num;
}
- (FGalleryPhotoSourceType)photoGallery:(FGalleryViewController *)gallery sourceTypeForPhotoAtIndex:(NSUInteger)index
{
return FGalleryPhotoSourceTypeLocal;
}
//Delegate method ends here
// My method to show gallery with my images
-(void)ShowGallery
{
localImages = [[NSMutableArray alloc]init];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDir = [paths objectAtIndex:0];
NSString *dataPath = [documentsDir stringByAppendingPathComponent:chatWithUser;
NSString *msgsColumn;
//database query to fetch all images name and then
while([results next])
{
msgsColumn = [results stringForColumn:#"msgs"];
if([[[msgsColumn componentsSeparatedByString:#"."]objectAtIndex:1]isEqualToString:#"jpg"])
{
[localImages addObject:[dataPath stringByAppendingPathComponent:msgsColumn]];
}
}
[database close];
localGallery = [[FGalleryViewController alloc]initWithPhotoSource:self];
[self.navigationController pushViewController:localGallery animated:YES];
}
For images path i used stringByAppendingPathComponent but I also tried stringByAppendingString to guide that images resides in documents directory but nothing is working.
Please assist me to figure out what is going wrong.
I found the solution, and here it is
https://github.com/gdavis/FGallery-iPhone/issues/15
Everytime I launch an app, I should to read 5 txt file where are stored some information; then the methods that read and stored data in array from these file should be write in my firstview controller (class of my first view) or in class appdelegate?
In the relevant view controller (probably viewDidLoad).
It would look something like this (untested):
- (void)viewDidLoad {
NSArray *fileNames = [NSArray arrayWithObjects:#"fileName1.txt", #"fileName2.txt", #"etc", nil];
NSMutableArray *fileStrings = [[NSMutableArray alloc] init];
for (int i = 0; i<[fileNames count]; i++) {
NSString *aFileName = [fileNames objectAtIndex:i];
NSString *aFilePath = [[NSBundle mainBundle] pathForResource:aFileName];
NSString *aFileContents = [[NSString alloc] initWithContentsOfFile:aFilePath];
[fileStrings addObject:aFileContents];
[aFileContents release];
}
myStrings = fileStrings; // Some array to store to
}
I am guessing this is configuration info that you are reading. I would suggest using a pList instead of using text files.
Apple has really optimized reading from & to a plist. Hope this helps...