When I go from my home segue into another I enter data into text fields. I have a navigation bar at the top and if I hit the back button to go to the home segue I lose all the data I entered. What is an efficient way of keeping the data in those fields?
Thank you.
Edit:
Maybe this helps, but when I load the screen it calls the method-
- (void)viewDidLoad {
NSLog(#"Testing viewDidLoad");
[super viewDidLoad];
// Do any additional setup after loading the view.
_YourName.delegate = self;
_Notes.delegate = self;
// Set up the scroll view.
_scroller.delegate = self;
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];}
but when I go back to the previous viewcontroller or hit the home button on the ipad it does not call:
- (void)viewDidUnload{
NSLog(#"Testing viewDidUnload");
[self setYourName:nil];
[self setPartnersName:nil];
[self setStartTime:nil];
[self setEndTime:nil];
[self setCurrentTemp:nil];
[self setCurrentWeather:nil];
[self setProjectName:nil];
[self setInstructorName:nil];
[self setClassNum:nil];
[self setWaypoints:nil];
[self setNotes:nil];
[super viewDidUnload];}
Also the data remains in the UITextFields and UITextViews if I hit the home button and then reopen the app. It just goes away if I segue back to the previous UIViewController.
- (IBAction)saveCurrent:(id)sender {
if([self checkFields]){
[self getEndingTime]; //gets the time when saved
// save all data to string using csv formatting
NSString *resultLine = [self getCSVformat];
NSArray *paths = NSSearchPathForDirectoriesInDomains
(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
// get today's date for file name ****DO DATE FORMATTER ONLY ONCE
NSDate *today = [NSDate date];
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"yyyy-MM-dd"];
NSString *todayDate = [dateFormatter stringFromDate:today];
//make a file name to write the data to using the documents directory:
NSString *fileName = [NSString stringWithFormat:#"%#/%#_%#_%#.csv",documentsDirectory,
todayDate,[[self ProjectName] text],[[self YourName] text]];
//save content to the documents directory
[resultLine writeToFile:fileName
atomically:NO
encoding:NSUTF8StringEncoding
error:nil];
}}
- (IBAction)retractKeyboard:(id)sender {
[self resignFirstResponder];}
When you push your second view controller, a new instance of the second view controller is created and this is what is shown. When you press the back button this instance is destroyed and the original view controller pops back. When you access the second view controller again, it is another new instance so all of the fields are empty.
If you press the home button your app is suspended, but the second view controller instance is still in memory, so if you return immediately to your app, the values are still there. If you ran some other apps then eventually iOS would terminate your app to releae the memory and you would see your app start from the beginning if you open it.
There are a couple of different approaches you can take to persist the data, depending on the type of data and what you need to do with it in your app. The simplest way is to store data in NSUserDefaults - This works for small amounts of data such as simple strings. For example
[[NSUserDefaults standardUserDefaults] setObject:self.projectName forKey:#"projectKey"];
will save a string. and
self.projectName = [[NSUserDefaults standardUserDefaults] stringForKey:#"projectKey"];
would read it back again. You could use the first line in an action handler for a "save" button and the second in viewDidLoad
If you need to store more complex data then you should look at Core-Data which provides database-style storage mapped to data classes or even online solutions like Parse.com
The other location where you can handle data save/load is in prepareForSegue:sender: - you can examine the segue name and access properties of the source and destination view controller
You will never lose your entered data unless you remove it or the container is removed from the superview.
Related
I have 2 views, a login view and a main view.
I use SWRevealViewController, and I want automatically display my menu at the startup of the app. I know how display my menu but I don't know how display it just once at startup.
I want to pass a simple String between my Login view and my Main view but without segue, and made a simple test :
if (myPreviousView == "LoginView")
{
// display my menu
}
Another method would be to use NSUserDefault to store your string, which than can be accessed from anywhere within the application.
So, you put your string into NSUserDefaults in your first view:
// Initialize the NSUserDefaults object and an array for data storage
NSUserDefaults *defsData = [NSUserDefaults standardUserDefaults];
NSMutableArray *myArray = [[NSMutableArray alloc] init];
// Add your string to the custom array
NSString *myString = #"My string.";
[myArray addObject:myString];
// Put the array back into UserDefaults for later use
[defsData setObject:myArray forKey:#"Key"]; // Key can be anything
[defsData synchronize];
Now, the array (and the string in it) is available anywhere. So, when you navigate to your second view controller, just initialize an NSUserDefaults and access the string:
NSUserDefaults* defsData = [NSUserDefaults standardUserDefaults];
NSArray *myArray = [defsData objectForKey:#"Key"];
NSLog("This is my stored string: %#", [myArray objectAtIndex:0]);
You can modify the init method of your second view controller to take a custom attribute when you subclass it. So, lets say you created a standard UIViewController (.h and .m files). You can modify the init method of this new class to your liking in the .h file:
- (instancetype)initWithString:(NSString *)string;
And then replace the standard init with the new one in the .m:
- (instancetype)initWithString:(NSString *)string {
}
So, when you call your view controller into existence, you just use this new init method and pass the string you wanted like this:
UIViewController *viewController = [[UIViewController alloc] initWithString:myString];
[self presentViewController:viewController animated:NO completion:nil];
This is a programmatical approach of course, but it should be applied to interface builder easily (unfortunately, as I never use interface builder, I don't know how exactly, but as I said, it should be fairly straightforward to anyone who uses it).
I'm trying to add a user for the first time app is opened. That adding response back to me with a some sort of password, what we call Access Token. The token is using to reaching the API services.
Anyway, beside that story, what my problem is for the first time app open, I can add the user and get the token and I saved it as NSUserDefault, but I can't reach to API Services since it tries to reach services with null. After refreshing or reopening the app or switching another view and back that view will solve the issue. But just for the first time, it can't reach the service.
The problem is here, obviously, when the app opens for the first time, before the adding operation is finished, I try to reach to service what cause it to stay null. So here is the code I have:
- (void)viewDidLoad {
[super viewDidLoad];
//Adding user.
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; // User informations.
NSString *accessToken = [defaults objectForKey:#"accessToken"];
if(accessToken == nil)
[APIClient AddUser];
//Adding the inital viewController.
UIViewController *vc = [self viewControllerForSegmentIndex:self.typeSegmentedControl.selectedSegmentIndex];
[self addChildViewController:vc];
vc.view.frame = self.contentView.bounds;
[self.contentView addSubview:vc.view];
self.currentViewController = vc;
}
Don't hang up with what I'm doing while adding the initial viewController, rather than that please just see what happens there as I add the user and then initialize the viewController.
Being asynchronous is the problem, so I'm looking a way to adding the user before the app actually started or launched.
I'm not sure in this case if I have to add the user in application:didFinishLauncingWithOptions, but I don't think so that is the problem.
Okay, so the first thing I would do is extract token related code to the function in view controller. Let's call it tokenReceived:
-(void) tokenReceived{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; // User informations.
NSString *accessToken = [defaults objectForKey:#"accessToken"];
if(accessToken == nil)
[APIClient AddUser];
//Adding the inital viewController.
UIViewController *vc = [self viewControllerForSegmentIndex:self.typeSegmentedControl.selectedSegmentIndex];
[self addChildViewController:vc];
vc.view.frame = self.contentView.bounds;
[self.contentView addSubview:vc.view];
self.currentViewController = vc;
}
In the view did load add the following:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(onGetToken)
name:#"getToken"
object:nil];
then add the new method:
- (void) onGetToken
{
[self tokenReceived]
}
and, ofcourse, when you get the token ( in app delegate or wherever you retrieve token ) send notification to observer:
// your code, saving to nsuserdefualts etc.
[[NSNotificationCenter defaultCenter]
postNotificationName:#"getToken"
object:self];
Explanation: It is a bad practice to block main thread with network - your app will not respond to any of the events and if you don't have 3g or wifi - you are stuck. User experience is bad, and that will drive users away.
On the other hand, this will open user screen, and you can put loader if you want till you notify the view controller that you actually got something. This way you will keep async nature of network and still refresh UI when you get one. If you are concerned that you will get token before opening view controller, you can add tokenReceived to view did load as well.
One more thing, you can send NSNotification with the object, and get one from NSNotification object, but in this case you just need to be notified that something happened, you don't need that data.
So I'm making a game that involves wireless communication between multiple iPhones, with one being the host. I am attempting to do so via the MultipeerConnectivity framework, and I've made a MCManager class (an instance of which I put into appDelegate so it's available throughout the app) to handle sending data from one system to another. This is how sending is implemented in my code:
- (void) sendState: (NSString*) str;
//used by the host to send commands to the other connected systems
{
if(appDelegate.mcManager.connected && iAmHost){
NSData *dataToSend = [str dataUsingEncoding: NSUTF8StringEncoding];
NSArray *allPeers = appDelegate.mcManager.session.connectedPeers;
NSError *error;
[appDelegate.mcManager.session sendData:dataToSend
toPeers:allPeers
withMode:MCSessionSendDataReliable
error:&error];
if (error) {
NSLog(#"%#", [error localizedDescription]);
}
}
}
and when the subordinate systems receive the data, MCManager sends the Notification Center a notification and my class, which is looking for that particular notification, grabs it and executes this:
-(void)didReceiveDataWithNotification:(NSNotification *)notification{
if(!iAmHost){
NSData *receivedData = [[notification userInfo] objectForKey:#"data"];
NSString *action = [[NSString alloc] initWithData: receivedData encoding:NSUTF8StringEncoding];
NSLog(#"Recieved:");
NSLog(action); //for debugging purposes, and figuring out timing
//decide how to act depending on the string given
if([action containsString:#"ChangeMaxScore"]){
//the string was formatted as, for example, "ChangeMaxScore105"
NSString* valueStr = [action substringFromIndex:14];
maxScore = (int)[valueStr integerValue];
[self changeMaxScore]; //this method changes the label text that shows the user the value of maxScore
}
else if([action containsString:#"ChangePlayerNo"]){
//strings are formatted as "ChangePlayerNo2" for the second segment in a segmented control with the segments "2", "3", "4"
//so it would be referring to four players
NSString *valueStr = [action substringFromIndex:14];
[playerNumberSegmentedControl setSelectedSegmentIndex: [valueStr integerValue]];
playerNumber = playerNumberSegmentedControl.selectedSegmentIndex + 2;
[self changePlayerNumber];
//Players can either be human or a type of AI (AI-1,AI-2,etc.)
//the segmented control where you choose this is invisible unless that player number is playing
//so this method sets that segmented control visible and interactable (by adding it to the view)
//and removes those segmented controls not in use from the view
}
else if([action containsString:#"ChangeP0State"]){
//changes the value of the first player's segmented control (Human, AI-1, AI-2, etc.
NSString* valueStr = [action substringFromIndex:13];
AIControl0.selectedSegmentIndex = (int)[valueStr integerValue];
}
...
else if([action containsString:#"StartGame"])
[self newGame];
//this method starts the game and, in the process, pushes another view controller
}
}
My issue is that these actions, on the receiving end, are very laggy. For changing the number of players, for instance, the receiver NSLogs "Received: ChangePlayerNo1", and the segmented control on-screen changes its selected segment to the second one, but the stuff that's supposed to show up at that point...doesn't. And when I send the "StartGame" command, the receiver NSLogs that it has received it, I have to wait thirty seconds for it to actually start the game like it was asked.
This delay makes it very hard to test whether my wireless methods are working or not (it works on the host's side, mostly because the host is changing them manually, not responsively - also, all of this works on the other side of my program, which is just the game without wireless support, with several players/AIs on a single screen) and, if not fixed, will definitely prevent the app from being used easily.
I'm curious what causes this and what I can do to fix it. Thank you!
I am reading some book and I stumbled this thing.
In my ViewController when a user clicks change date button following
code is called:
- (IBAction)changeDate:(id)sender {
DateViewController *vc = [[DateViewController alloc] init];
[vc setItem: item];
[[self navigationController] pushViewController:vc animated:YES];
}
item is a pointer to a custom class object, which has ivar of type NSDate *;
Now, inside DateViewController when user already picked new date and wants
to navigate to previous view, I have following code:
- (void)viewWillDisappear:(BOOL)animated
{
NSLog(#"%#", [datePicker date]);
item.dateCreated = [datePicker date]; // get selected date
}
This code works and when user goes back from above code change is reflected
in item data structure and user can see new date. However, if I change above code, to following code, it doesn't work anymore, any clues why?
(This does NOT work):
- (IBAction)changeDate:(id)sender {
DateViewController *vc = [[DateViewController alloc] init];
vc.userDate = currentItem.dateCreated;
[[self navigationController] pushViewController:vc animated:YES];
}
DateViewController:
- (void)viewWillDisappear:(BOOL)animated
{
NSLog(#"%#", [datePicker date]);
self.userDate = [datePicker date];
}
In the first case, item is a mutable instance, because you can change the date that it contains. In the second case you are supplying the NSDate itself, which is immutable.
So, in the first case, you pass a reference to item which can be edited and these edits are available later.
But, the second case doesn't edit the original date, it just stores the chosen date into a property on the view controller which is in the middle of being dismissed.
Generally it is better to make the communication clear, so the view controller would pass the chosen date back to the caller by delegation or provide a property (like in your second case) that can be queried once the selection is made. Your first option is effectively hiding the data exchange by sharing the instance item while the second view controller is on display.
Immutable means that the object itself (its contents) can not be changed. It does not prevent any reference to the object from being changed. If we use arrays (where there are mutable an immutable versions) to demonstrate:
NSArray *a = [NSArray array];
NSArray *b = a;
[b editSomething]; // illegal (not a true method name but just an example of something you might want to try)
b = nil; // just nils b, no affect on a at all
And
NSMutableArray *a = [NSArray array];
NSMutableArray *b = a;
[b addObject:#"String"]; // edits a, because a and b are the same object
b = nil; // just nils b, no affect on a at all
The NSArray is like the situation where you just pass the NSDate. The NSMutableArray is like the situation where you pass item (because you can change the contents).
I have a NavigationController and one of the tabs is supposed to load a ViewController.
This ViewController (1), when loaded on "viewDidLoad" does some stuff and then pushes a new ViewController (2). The thing is that after ViewController (1) has already passed through viewDidLoad, it won't pass through it again, unless the app is restarted.
Could you guys please refer a clever way to to this?
Here's what I am really doing:
- (void)viewDidLoad
{
// Keep track of cash using NSUserDefaults
BOOL dreceived[63];
int rightData;
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
//Load cash switches
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
NSData *data = [prefs objectForKey:#"dreceived"];
memcpy(&dreceived, data.bytes, data.length);
for(int n = 72; n >= 1; n = n - 1)
{
if(dreceived[n-1]==1)
{
rightData = n;
}
}
NSLog(#"Right Data %d", rightData);
CashItem *c = [cashflow objectAtIndex:rightData];
// Go for details
CashDetailedViewController *cdetail = [[[CashDetailedViewController alloc] init] autorelease];
cdetail.cash = c;
cdetail.navigationItem.hidesBackButton = YES;
[self.navigationController pushViewController:cdetail animated:YES];
}
The thing is, this code is never called again. And if I touch the tab twice, a blank view is displayed (th original xib view).
Thanks!
It sounds like you would want to use viewWillAppear. It is called every time that your view controller is about to be onscreen.
Although, based on what you've posted, you may want to rethink what your doing. Having a view controller that immediately presents another view controller should like it would lead to a confusing user experience.
Put your code in - (void)viewWillAppear instead
Try calling
[yourViewController.view setNeedsDisplay];
Or you could spin the code out to a seperate method and call it in viewDidLoad and viewDidAppear:animated