uiswitch in uitableview persistence when close and reopen the app? [duplicate] - ios

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!

Related

Append New Input to plist in Documents Folder

After much searching of SO I've been able to come up with code to correct previous failures. What I am trying to accomplish is accepting user input from a UITextField and add that input to my TableView, which is populated from a plist. The root value of my plist is dictionary and I would like to keep it as such. My problem is when writeToFile is called, I'm completely over writing the existing values instead of inserting, adding or appending to the existing data. It doesn't appear that my attempt to combine the two dictionaries is working, since only the new value is being stored to the plist. Any insight as to where I'm going wrong? The following code is what I have
//////////********** Add New Cell When OK is Chosen
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
UITextField *newDevice = [alertView textFieldAtIndex:0];
NSLog(#"newDevice - %#",newDevice.text);
if (alertView.tag == 1 && buttonIndex == 1){
NSMutableDictionary *input = [[NSMutableDictionary alloc]init];
[input setObject:[NSArray arrayWithObject:newDevice.text] forKey:#"Room"];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *path = [documentsDirectory stringByAppendingPathComponent:#"Test.plist"];
NSMutableDictionary *dictionary = [[NSMutableDictionary alloc] initWithContentsOfFile:path];
// Combine the two Dictionaries to create one
[dictionary addEntriesFromDictionary:input];
// Write Combined Dictionary to plist
[dictionary writeToFile:path atomically:YES];
// Add Newly Created text to Table because reload table doesn't do it
[myTableData addObject:newDevice.text];
// Reload Table Data even though it seems useless
[myTable reloadData];
}
}
I guess you overwrote the values of the key Room.
NSString *path = [documentsDirectory stringByAppendingPathComponent:#"Test.plist"];
NSMutableDictionary *dictionary = [[NSMutableDictionary alloc] initWithContentsOfFile:path];
// Get exist input from plist
NSMutableArray *inputFromPlist = dictionary[#"Room"];
// add new object to input
[inputFromPlist addObject:newDevice.text];
// set Room with modified object
[dictionary setObject:inputFromPlist forKey:#"Room"];
// Write Combined Dictionary to plist
[dictionary writeToFile:path atomically:YES];
// do something after saving plist ...

Load array and display based on state of uiswitch

Im trying to develop an app that displays a random truth or dare type question, however the user has the ability to turn off truths or dares in option. I have successfully managed to get the app to display a random quote from a plist file from either the truth or dare array also i have managed to program two switch buttons in the user options view controller.
My problem is how would i go about displaying only a truth or dare or both if the user has turned on of the uiswitchs off?
- (IBAction)button:(id)sender
{
if (!self.plistArray)
{
NSString *path = [[NSBundle mainBundle] pathForResource:
#"data" ofType:#"plist"];
NSUserDefaults *defaults =[NSUserDefaults standardUserDefaults];
if ([[defaults objectForKey:#"truthonoff"] isEqualToString:#"YES"])
{
NSDictionary *plistDict = [[NSDictionary alloc] initWithContentsOfFile:path];
NSArray *plistArray1 = plistDict[#"truth"];
}
if ([[defaults objectForKey:#"dareonoff"] isEqualToString:#"YES"])
{
NSDictionary *plistDict2 = [[NSDictionary alloc] initWithContentsOfFile:path];
NSArray *plistArray2 = plistDict2[#"dare"];
}
self.plistArray = [[plistArray1 arrayByAddingObjectsFromArray:plistArray2] mutableCopy];
}
NSLog(#"%#", plistArray);
//check to see if array is empty and display message
if ([plistArray count] == 0)
{
self.text.text = #"array empty";
}
else
{
//display random quote from array
int randV = arc4random() % self.plistArray.count;
self.text.text = self.plistArray[randV];
[self.plistArray removeObjectAtIndex:randV];
}
}
That is my attempt however it will not run and i have the feeling it wont ddo the job i need.
Basicly i need it to display only truth if the user has selected that to true or only dare if that is selected or both if both are set to true.
EDIT
sorry the problem with the above code is the plist isnt being loaded and it is scipping straight to if array ==0 {
How do i ensure it loads the array and then checks which arrays to load from the plist file?
Any help is greatly appreciated
This is the code before i tried to add if statements. Im so confussed how best to do this
- (IBAction)shownext:(id)sender {
//load array and check then cycle through this untill array is empty. Array will add two arrays from plist file.
if (!self.plistArray) {
NSString *path = [[NSBundle mainBundle] pathForResource:
#"data" ofType:#"plist"];
NSDictionary *plistDict = [[NSDictionary alloc] initWithContentsOfFile:path];
NSArray * plistArray1 = plistDict[#"truth"];
NSDictionary *plistDict2 = [[NSDictionary alloc] initWithContentsOfFile:path];
NSArray *plistArray2 = plistDict2[#"dare"];
self.plistArray = [[plistArray1 arrayByAddingObjectsFromArray:plistArray2] mutableCopy];
}
NSLog(#"%#", plistArray);
//check to see if array is empty and display message
if ([plistArray count] == 0) {
self.text.text = #"array empty";
}
else {
//display random quote from array
int randV = arc4random() % self.plistArray.count;
self.text.text = self.plistArray[randV];
[self.plistArray removeObjectAtIndex:randV];
}
}
First, if you have a switch for truth and one for dare I hope you have something in place to deal with when the user turns both switches off and doesn't understand why they get nothing (trust me it will happen).
For the rest I'm not sure exactly how you app works but I will take a guess. I'm thinking you have a utility style app with the main UI in one view and then an info button that flips to a second view where the switches are. I'm also guessing that there is a button in the main view that retrieves a truth or dare string. My final assumption, based on your code above, is that when the user changes the state of a switch that writes a user default that you've use a #define to keep out spelling mistakes.
When your view loads you should load both arrays in case the user changes their mind in the middle of using your app and turns on both options or changes from one to the other. Depending on how many entries you have in each of those arrays you might consider creating a combined array as well to simplify things.
When the button is pressed you should then look at the defaults and see if you need to look at both arrays or just one (the below is pseudo code)
if(truth && dare) {
// if you have a combined array pick a random element from it.
// otherwise first randomly pick one of the arrays to pick from.
}
else if (truth) {
// get a random element from the truth array
}
else {
// get a random element from the dare array
}
Also, your current checks of the switch values will always return no unless you are doing extra work in the switch view controller. You should be using [defaults setBool:<UISwitch.isOn> forKey:<#definedKeyHere>] and [defaults boolForKey:<#definedKeyHere>].
It would really help to know what part isn't working. For one thing, it might help to store your flags as NSNumber objects instead of strings (could your string comparison be failing?). Then you could do something like:
if ([[defaults objectForKey:#"truthonoff"] boolValue])
Use a literal to add the actual NSNumber - #YES or #NO.
Consider changing your logic to something like:
VERIFY CODE - doing this freehand:
if (!self.plistArray)
{
self.plistArray = [NSMutableArray array];
NSString *path = [[NSBundle mainBundle] pathForResource:
#"data" ofType:#"plist"];
// why are you loading this twice?
NSDictionary *plistDict = [[NSDictionary alloc] initWithContentsOfFile:path];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
if ([[defaults valueForKey:#"truthonoff"] boolValue])
{
[self.plistArray addObject:plistDict[#"truth"]];
}
if ([[defaults valueForKey:#"dareonoff"] boolValue])
{
[self.plistArray addObject:plistDict[#"dare"]];
}
}
I am assuming that your code to load the Plists is working. Verify all your keys match what's in the Plist. Set a breakpoint and verify.
Calling a method on a nil object is a no-op in Objective C. So, it'll happily ignore calls to nil objects without telling you. Verify you have what you think you have.
Also, here:
//display random quote from array
int randV = arc4random() % self.plistArray.count;
self.text.text = self.plistArray[randV];
[self.plistArray removeObjectAtIndex:randV];
Consider using arc4random_uniform(self.plistArray.count) as it avoids modulo bias in the generator.
Now, this just gives you say 0 or 1 if you have two elements. Are you sure the two dictionary keys, "truth" and "dare" actually point to arrays?
Ok, everything working to this point. Now, you have an array of ARRAYS! So you need to randomly pick a question array, and THEN randomly pick a question.
Something like:
//get random array
int randArrayIndex = arc4random_uniform(self.plistArray.count);
NSArray* questionArray = self.plistArray[randArrayIndex];
//get random question
int randQuestionIndex = arc4random_uniform([questionArray count]);
NSString* randomQuestion = questionArray[randQuestionIndex];
self.text.text = randomQuestion;
// remove question
[questionArray removeObjectAtIndex:randQuestionIndex];
Something like that anyway. Of course, assuming you are storing NSStrings in those Plist arrays.

Global NSString without alloc access only in 2 place or in two method and in 3rd place it crash the application

Here NSString *documentsPath declare in globally.
#import "DetailViewController.h"
#import "Information.h"
#interface DetailViewController ()
{
NSString *documentsPath;
NSArray *paths;
}
assigning and accessing in viewDidLoad method.
- (void)viewDidLoad
{
NSLog(#"Enter viewDidLoad DetailViewController");
paths = NSSearchPathForDirectoriesInDomains (NSDocumentDirectory, NSUserDomainMask, YES);
**documentsPath** = [paths objectAtIndex:0];
NSString *plistPath = [**documentsPath** stringByAppendingPathComponent:#"InfoDetail.plist"];
}
also accessing in settingCellImage method
-(void)settingCellImage:(UITableViewCell *)cell noOfRow:(int)row
{
NSLog(#"Enter in settingCellImage method");
NSLog(#"Doc Path Cell ===>%#",documentsPath);
NSString *imagePath = [**documentsPath** stringByAppendingFormat:[NSString stringWithFormat:#"/%d.png",row]];
}
But when i use it in didSelectRowAtIndexPath method it crash the application.
(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
Information *information = [[[Information alloc]initWithNibName:#"Information" bundle:nil]autorelease];
information.infoDict = [test objectAtIndex:indexPath.row];
NSLog(#"Selected row No is ==> %d",indexPath.row);
NSLog(#"Using #=== %d",indexPath.row);
NSLog(#"documents path == %#",**documentsPath**);// here it gives EXE_BAD_ACCESS.
NSString *selectedImagePath = [documentsPath stringByAppendingFormat:[NSString stringWithFormat:#"/%d.png",indexPath.row+1]];
}
I'm new in iPhone development and it is my demo project. so please help me as soon as possible.
and please give me answer in easy way so i can understand very fast. And yes i know already that i'm not alloc init documentsPath direct assign in viewDidLoad method, and i want to know that why it will access in 2 places and in 3rd place it will crash the application. I hope someone can help me. Thank you!
Since you're not using ARC, you have to make sure you are retain/releasing/autoreleasing all your objects. What's happening is that your documentsPath got released and now you are accessing a released object. You could assign the string like so:
documentsPath = [[NSString stringWithString:[paths objectAtIndex:0]] retain];
And then you have to release it in your dealloc call.
I think the problem is you use stringByAppendingFormat two times for documentsPath and then at didSelectRowAtIndexPath when you access to it. App will crash.
Use ARC.
Even if you do use ARC, you should understand the basics of object ownership. Do a search on "Basic Memory Management Rules" in Xcode and read that entire section. Also read the section below, "Practical Memory Management"
To summarize, if you need an object to stick around, you need to retain it/maintain a strong reference to it. Every retain needs to be balanced with a release. This means that when you are done with an object that you have retained, you must release it.
Your code is not following those rules, so you're crashing.
documentsPath has static assign and then i manipulate it with stringByAppendingFormat it creates problem because documentsPath is a immutable and then also you are manipulate it so the application is crash. so the manipulations of immutable object generate problem.Because NSString object by default immutable object you can not change or manipulate assigned string.
Try This instead of when you want to use stringByAppendingFormat .
NSString *imagePath = [[NSString alloc] initWithFormat:#"%#/%d.png", documentsPath,indexPath.row+1];
Use NSString initWithFormat and don't manipulate String.

Calling reloadData in UITableViewController not working

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.

IOS: read txt file when I launch an app

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...

Resources