saving changed text in textview loaded from NSMutableDictionary - iphone

I have a UITextView in a DetailViewController with some text which is loaded from a NSMutableDictionary in the MainViewController. Here is the code for that;
- (void) coverflowView:(TKCoverflowView*)coverflowView coverAtIndexWasDoubleTapped:(int)index{
DetailViewController *detailViewController = [[DetailViewController alloc] initWithNibName:#"DetailViewController" bundle:nil];
if ((int)index == 0) {
NSMutableDictionary *myDictionary = [NSMutableDictionary dictionaryWithObjectsAndKeys:
#"This is some text", #"textkey", nil];
detailViewController.myDictionary = myDictionary;
}
I have loaded the string from this into my DetailViewController with this code;
self.myText.text = [(NSMutableDictionary *)myDictionary objectForKey:#"textkey"];
Also in my viewDidLoad I have created a RightBarButton named 'Save' which I use to hide the keyboard when the viewer is done editing. I would like this button to also save the changes the viewer enters into the UITextView (as well as the original text).
This is the code for the rightBarButton;
UIBarButtonItem *saveButton =[[UIBarButtonItem alloc]
initWithTitle:#"Save"
style:UIBarButtonItemStyleBordered
target:self
action:#selector(textViewDidChange:)];
self.navigationItem.rightBarButtonItem = saveButton;
Lastly I have code which envokes the textVidewDidChange and hides the keyboard. I am trying to also have it save the changes to the textView but it doesn't.
-(void)textViewDidChange:(UITextView *)textView;
{
if( textView == myText )
[myDictionary setValue:myText.text forKey:#"textkey"];
[myText resignFirstResponder];
}
Can anyone help me accomplish this. I simply want to save the changes to the UITextView back to the NSMutableDictionary. (or maybe not so simply)
I have changed the button to
`UIBarButtonItem *saveButton =[[UIBarButtonItem alloc]
initWithTitle:#"Save"
style:UIBarButtonItemStyleBordered
target:self
action:#selector(didChangeValueForKey:)];
self.navigationItem.rightBarButtonItem = saveButton;`
and other code to ;
`-(void)didChangeValueForKey:(NSString *)key{
NSError *error;
[myDictionary setValue:[self myText].text forKey:#"textkey"];
[myText resignFirstResponder];
NSLog(#"%#", [myDictionary valueForKey:#"textkey"]);
}
My NSLog shows the changes but when I reopen the app they are gone, not saved. Can one save directly to the NSMutableDictionary?
I read a lot on persistent data. Thought NSData or Plist but try as I may not doing well.
Can someone suggest a good tutorial on this?
I looked at suggested link and added this (bold part) to my code.
`-(void)didChangeValueForKey:(NSString *)key{
/* NSString *path = [NSHomeDirectory() stringByAppendingPathComponent:[(NSMutableDictionary *)myDictionary objectForKey:#"textkey"]];*/
NSError *error;
[myDictionary setValue:[self myText].text forKey:#"textkey"];
**[myDictionary writeToFile:#"textkey" atomically:YES];**
[myText resignFirstResponder];
NSLog(#"%#", [myDictionary valueForKey:#"textkey"]);
}`
As you can see I also tried getting the path using [NSHomeDirector] above and then replaced
#"textkey" with path. I can still see the changes in NSLog (either way) but there is no change when I reload the view or relaunch the app.
I have change things so that I am saving a text file of the text in the textview with the name gotten from the dictionary so that each time a different detailView is loaded depending on the image selected in the mainViewController.
This is my dictionary entry in the mainViewController;
`if ((int)index == 0) {
NSMutableDictionary *myDictionary = [NSMutableDictionary dictionaryWithObjectsAndKeys:
//here is where I want to access the text file name to load in the UITextView in my DetailView
#"first View Text", #"textkey2",
//This is where I give the text file of the changed text its name for each different view of the DetailView
#"first View Text", #"textkey3",nil];
detailViewController.myDictionary = myDictionary;
}`
Next is the code I use to save the changes to the textView using the UIbarbuttonright
`- (void)saveAction:(id)sender {
[self.myText2 resignFirstResponder];
NSArray *documentPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [documentPaths objectAtIndex:0];
NSString *documentTXTPath = [documentsDirectory stringByAppendingPathComponent:[(NSMutableDictionary *)myDictionary objectForKey:#"textkey3"]];
NSString *savedString = myText2.text;
NSLog(#"The Save file is:%#", savedString);
[savedString writeToFile:documentTXTPath atomically:YES
encoding:NSUTF8StringEncoding error:NULL];
}`
I have checked this and the file is saved in the documents folder under the name of First View Text and it does contain the changed text.
I am having problems loading the text file contents into the UITextView.
Using the code I have I get the path to the textkey2 object (First Text View) not the contents of the file.
`NSString *textName = [myDictionary objectForKey:#"textkey2"];
NSArray *paths2 = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
NSUserDomainMask, YES);
NSString *documentsDirectory2 = [paths2 objectAtIndex:0];
NSString *fullPath2 = [documentsDirectory2 stringByAppendingPathComponent:textName];
/* self.myText2.text = fullPath2;*/
self.myText2.text = [NSString stringWithContentsOfFile:fullPath2 encoding:NSUTF8StringEncoding error:NULL];`
I replaced this last line with the one that isn't commented out and it works fine. For anyone who want to know.

You need to implement data persistence, e.g. saving the text into permanent storage that you can retrieve again when you reload the view later on.

Related

How to display the created file name and contents on textview?

How to display the created file name and contents on textview? This is my code. Iam able to display the file name and its contents in NSLog but not on textview though i have given the outlet for textview. Please help me with this issue.
-(IBAction) open: (id) sender
{
// Create instance of UITextView
textView = [UITextView alloc];
initWithFrame:[[UIScreen mainScreen] applicationFrame]];
NSString *homeDir = NSHomeDirectory();
// Add text
textView.text= homeDir;
NSLog(#"%#", homeDir);
textfield.text= #"output will go here..";
NSString *myFilePath = [[NSBundle mainBundle]
pathForResource:#"Myfile"
ofType:#"txt"];
NSString *myFileContents = [NSString stringWithContentsOfFile:myFilePath
encoding:NSUTF8StringEncoding
error:nil];
NSString *s = [NSString stringWithFormat:#"myFilePath: %#, Contents of file:%#",myFilePath, myFileContents];
NSLog(#"%#", s);
textView.text= s;
}
Does the textView work at all? Have you tried:
textView.text = #"TestText"? If the textView remains empty you need to check the outlet/ check whether the textView is added to the ViewController properly. I think this should solve your problem.
There is an issue with your text view setup.
If an outlet is connected to the text view, you do not need to create it in code (which you are doing).
If you create it in code, you have to add it as a subview to the desired view (which you are not doing).
I think you forgot to add the textview in your parentview; I assume you are creating runtime textview. Just add this line in your code.
[self.view addSubview:textView];

Why isnt my iOS data persisting with NSKeyedArchiver?

Im just working on what should be the "finishing touches" of my first iPhone game. For some reason, when I save with NSKeyedArchiver/Unarchiver, the data seems to load once and then gets lost or something.
I'm trying to save 2 objects with the same archiver: an NSMutableDictionary levelsPlist, and an NSMutableArray categoryLockStateArray. The are set as nonatomic, retain properties in the header file.
Here's what I've been able to deduce:
The object saved as the NSMutableDictionary always persists. It works just fine.
When I save in this viewController, pop to the previous one, and then push back into this one, the data is saved and prints as I want it to.
But when I save in this viewController, then push a new one and pop back into this one, the categoryLockState is lost.
Any idea why this might be happening? Do I have this set up all wrong? I copied it from a book months ago. Here's the methods I use to save and load.
- (void) saveGameData {
NSLog(#"LS:saveGameData");
// SAVE DATA IMMEDIATELY
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *gameStatePath = [documentsDirectory stringByAppendingPathComponent:#"gameState.dat"];
NSMutableData *gameSave= [NSMutableData data];
NSKeyedArchiver *encoder = [[NSKeyedArchiver alloc] initForWritingWithMutableData:gameSave];
[encoder encodeObject:self.categoryLockStateArray forKey:#"categoryLockStateArray];
[encoder encodeObject:self.levelsPlist forKey:#"levelsPlist"];
[encoder finishEncoding];
[gameSave writeToFile:gameStatePath atomically:YES];
NSLog(#"encoded catLockState:%#",categoryLockStateArray);
}
- (void) loadGameData {
NSLog(#"loadGameData");
// If there is a saved file, perform the load
NSMutableData *gameData = [NSData dataWithContentsOfFile:[[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:#"gameState.dat"]];
// LOAD GAME DATA
if (gameData) {
NSLog(#"-Loaded Game Data-");
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:gameData];
self.levelsPlist = [unarchiver decodeObjectForKey:#"levelsPlist"];
categoryLockStateArray = [unarchiver decodeObjectForKey:#"categoryLockStateArray"];
NSLog(#"decoded catLockState:%#",categoryLockStateArray);
}
// CREATE GAME DATA
else {
NSLog(#"-Created Game Data-");
self.levelsPlist = [[NSMutableDictionary alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:kLevelsPlist ofType:#"plist"]];
}
if (!categoryLockStateArray) {
NSLog(#"-Created categoryLockStateArray-");
categoryLockStateArray = [[NSMutableArray alloc] initWithCapacity:[[self.levelsPlist allKeys] count]];
for (int i=0; i<[[self.levelsPlist allKeys] count]; i++) {
[categoryLockStateArray insertObject:[NSNumber numberWithBool:FALSE] atIndex:i];
}
}
// set the properties of the categories
self.categoryNames = [self.levelsPlist allKeys];
NUM_CATEGORIES = [self.categoryNames count];
thisCatCopy = [[NSMutableDictionary alloc] initWithDictionary:[[levelsPlist objectForKey:[self.categoryNames objectAtIndex:pageControl.currentPage]] mutableCopy]];
NUM_FINISHED = [[thisCatCopy objectForKey:kNumLevelsBeatenInCategory] intValue];
}
I can only guess that it is related to the fact that the viewController is unloaded and reloaded when you pop then push back in, but my viewDidLoad code makes no mention of either of these variables. They are called from the viewDidAppear method.
Thanks for any help you can offer!

How to show UIActivityIndicationView when loading data

I am trying to show an indicator view when loading data in viewDidLoad but the loading starts before i display the indicator, i guess that is because the view is not loaded until after. I have been reading a bit about this but just cannot get it to work.
What i would want is to display the activity indicator during the loading of the files into the database.
I have been doing testing so the code structure may look a bit weird.
Could someone nice please give me a hint, or a link, how to fix this so the activity indicator is shown when/if data is loaded from the .txt files into the DB?
- (void)viewDidLoad {
[super viewDidLoad];
self.title = #" ";
loadActivityIndicator.hidden = TRUE; // Hide UIActivityIndicationView at start
[self loadDataIfNeeded];
}
-(void)loadDataIfNeeded {
NSFileManager *fileManager = [NSFileManager defaultManager];
myFileArray = [[NSMutableArray alloc]init];
//======SET THE FILE INPUT NAME======//
qFileTxtName = #"110615";
[myFileArray addObject:qFileTxtName];
//===================================//
// CHECK IF THE FILE EXISTS
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *path = [documentsDirectory stringByAppendingPathComponent:#"checkQuestionFile.plist"];
if([fileManager fileExistsAtPath:path]) {
NSArray *readKeyFileName = [[NSArray alloc] initWithContentsOfFile:path];
if ([[readKeyFileName objectAtIndex:0] isEqualToString:qFileTxtName]) {
//==== THERE IS NO UPDATED QUESTION .txt FILE ====//
}
else {
//==== THERE IS AN UPDATED QUESTION .txt FILE ====//
// SET UP UIActivityIndicator view to show that work is ongoing
loadActivityIndicator.hidden = FALSE;
[loadActivityIndicator startAnimating];
//==== SET UP PATH FOR ALL FILES====//
NSString *aString = [[NSString alloc]init];
aString = [#"questions_famquiz_hard_" stringByAppendingString:qFileTxtName];
NSString *path1 = [[NSBundle mainBundle] pathForResource:aString ofType:#"txt"];
aString = [#"questions_famquiz_medium_" stringByAppendingString:qFileTxtName];
NSString *path2 = [[NSBundle mainBundle] pathForResource:aString ofType:#"txt"];
aString = [#"questions_famquiz_easy_" stringByAppendingString:qFileTxtName];
NSString *path3 = [[NSBundle mainBundle] pathForResource:aString ofType:#"txt"];
AccessQuestionsDB *accessQuestionDataFunction = [AccessQuestionsDB new];
idCounter = [accessQuestionDataFunction populateTheDatabase: path1 theID:0 firstTime: YES];
idCounter = [accessQuestionDataFunction populateTheDatabase: path2 theID:idCounter firstTime: NO];
idCounter = [accessQuestionDataFunction populateTheDatabase: path3 theID:idCounter firstTime: NO];
//==UPDATE THE PLIST==//
[myFileArray writeToFile:path atomically: TRUE];
// Stop UIActivityIndicator as activity is over
loadActivityIndicator.hidden = TRUE;
[loadActivityIndicator stopAnimating];
}
} else {
//== If file not found write a new file ==//
[myFileArray addObject:qFileTxtName];
[myFileArray writeToFile:path atomically: TRUE];
}
}
PROBLEM SOLVED
I replaced the call
[self loadDataIfNeeded];
with;
[NSThread detachNewThreadSelector:#selector(loadDataIfNeeded) toTarget:self withObject:nil];
to achieve multi threading according to the recommendation from Jonah :-)
You are loading your data synchronously in the main thread. That means that the main thread, the one responsible for drawing the UI, is busy loading your data and will not have a chance to update your view until after your loadDataIfNeeded method finishes (at which point you don't want to show your activity indicator to be visible anymore anyway).
Display your activity indicator on the main thread but then allow the main thread's run loop to continue and instead perform expensive operations (like loading data or performing network requests) asynchronously on a secondary thread.
Look at NSThread, NSObject's -performSelectorInBackground:withObject:, and NSOperationQueue for different options for performing tasks off of the main thread.
The book "IOS Recipes" by Matt Drance has a recipe that talks about how to do this best. You can get this recipe as a free excerpt from the book at http://media.pragprog.com/titles/cdirec/activity.pdf
You can find the source code for the recipe on the book page at http://pragprog.com/titles/cdirec/ios-recipes
In your viewDidLoad add
UIActivityIndicatorView *activityView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
activityView.frame = CGRectMake(0, 0, 44, 44);
activityView.center = self.view.center;
activityView.tag = 99;
[activityView startAnimating];
[self.view addSubview: activityView];
[activityView release];
Then in your load method after you finish loading just remove your activity indicator from super view.
So in your loadDataIfNeeded after loading add
[[self.view viewWithTag:99] removeFromSuperview];

iPhone - track back button event

I have a tableView which lists the contents of my document directory. I have some zip files in that. If I touch a file in the tableView, the corresponding zip file is unzipped and extracted in a temporary directory(newFilePath in my case). The contents unzipped is listed in the next tableView. When I touch the back button, the contents in the directory is listed again.
For example, consider that I have four zip files in my document directory.
songs.zip, videos.zip, files.zip, calculation.zip
When I run the application, all the four files are listed in the tableView. When I touch songs.zip, this file is extracted in the newFilePath and its contents are pushed to the next tableView. When I touch back, the previous tableView, i.e, the four files in the document directory are listed again. Everything works perfect.
The problem is, the extracted files in the newFilePath remains there itself. They occupy the memory unnecessarily. I want them to be removed from that path when I touch the back button, i.e, I want to make newFilePath empty when the back button is touched.
I tried for it. But, no use. I tried removeItemAtPath: method in viewWillAppear: and also in viewWillDisappear:. But it didnt work in both the cases.
Is there any other method to track the action of the back button? I want an event to take place when the back button is touched. So please help me by sharing your ideas. Here is my code for your verification.
This is my didSelectRowAtIndexPath:
NSString *filePath = //filePath
if([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
NSLog(#"File exists at path: %#", filePath);
} else {
NSLog(#"File does not exists at path: %#", filePath);
}
ZipArchive *zip = [[ZipArchive alloc] init];
NSString *newFilePath = //newFilePath
[[NSFileManager defaultManager] createDirectoryAtPath:newFilePath withIntermediateDirectories:NO attributes:nil error:nil];
BOOL result = NO;
if([zip UnzipOpenFile:filePath]) {
//zip file is there
if ([zip UnzipFileTo:newFilePath overWrite:YES]) {
//unzipped successfully
NSLog(#"Archive unzip Success");
result= YES;
} else {
NSLog(#"Failure To Extract Archive, maybe password?");
}
} else {
NSLog(#"Failure To Open Archive");
}
iDataTravellerAppDelegate *AppDelegate = (iDataTravellerAppDelegate *)[[UIApplication sharedApplication] delegate];
//Prepare to tableview.
MyFilesList *myFilesList = [[MyFilesList alloc] initWithNibName:#"MyFilesList" bundle:[NSBundle mainBundle]];
//Increment the Current View
myFilesList.CurrentLevel += 1;
viewPushed = YES;
//Push the new table view on the stack
myFilesList.directoryContent = [AppDelegate getTemporaryDirectoryItemList:newFilePath];
[myFilesList setTitle:detailedViewController.strName];
[self.navigationController pushViewController:myFilesList animated:YES];
[myFilesList release];
Thank you for your answers.
for creating your own action you need custom back button.But i think viewWillDisAppear also can do solve your problem.
use this code for solving your problem
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectoryPath = [paths objectAtIndex:0];
NSString *databaseFile = [documentsDirectoryPath stringByAppendingPathComponent:#"newFilePath"];
NSFileManager *fileManager = [NSFileManager defaultManager];
[fileManager removeItemAtPath:databaseFile error:NULL];
This may help you.
Edit:
NSString *documentsDirectoryPath = [paths objectAtIndex:0];
NSString *secondaryDirectoryPath = [secondaryDirectoryPath stringByAppendingPathComponent:#"secondary"];
NSString *databaseFile = [documentsDirectoryPath stringByAppendingPathComponent:#"newFilePath"];
use these lines in your code if you have directory inside the document directory.
you can add the custom back button on Navigation Bar. Create the method, which will fire on click event of Back button.

How to save Data in text file when user chooses a button

I want to have user select between two different buttons on a view, If the the user selects the imageOne, a label is created called "imageOne", then I'll save the label into an array using the following technique:
- (IBAction)saveData {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *choice = [NSString stringWithFormat:#"%#/userschoice", documentsDirectory];
NSMutableArray *array = [[NSMutableArray alloc] init];
[array addObject:imageOne.text];
[array writeToFile:choice atomically:NO];
}
I'm trying to avoid using the Picker and a Tableview as I only have two choices and it will be easier for user to choose with a button. any guidance would be appreciated.
Thanks.
Michael
Create the button, either in Interface Builder or in code
Hook up the button's delegate to point to the controller
Implement a method to do something in the controller (see link from number 2)
Save data