Passing data through ViewControllers - ios

I'm developing an app that will support multiple languages and I'm looking for the best way to set the different languages.
The app works with a UINavigationController. In the first ViewController you can select the language pressing a UIButton and then in the following view controllers the labels' texts would be changed to the corresponding language.
The way I'm doing it right now is by changing the value of a BOOL property when I create the instance of the new ViewController depending on the UIButton sender tag.
FirstViewController.m
-(void)goToSecondVC{
SecondViewController *secondVC = [[SecondViewController alloc]init];
if ([sender tag] == 1) {
secondVC.english = YES;
}else{
secondVC.english = NO;
}
[self.navigationController pushViewController:startScreenVC];
}
SecondViewController.m
-(void)viewWillAppear:(BOOL)animated{
if(self.english){
self.myLabel.text = #"This text will be in English";
}else{
self.myLabel.text = #"This text will be in Spanish";
}
I know this is probably not the best way to achieve this task. What would you recommend, notifications, delegation, singletons? I'm looking for a kind of global variable that could be written and read from every ViewController

You should be using localization for this.
You can get the language like this:
NSString *language = [[NSLocale currentLocale] objectForKey: NSLocaleLanguageCode];
Take a look at this this tutorial for localization:
http://www.raywenderlich.com/2876/localization-tutorial-for-ios
or this SO ansawer

The implementation is straightforward and correct.
Since you want to have this information known to every view controller, a better approach is to use KVO and a store for the language info value.
For example, save it to NSUserDefaults. Then from any view controller your could access it.
Then if some view controller wants to get notification when this value gets changed, it could observe the NSUserDefaults object for that value. (with Storyboard, you could use a Shared User Defaults Controller).

If you want to access the current language setting from any place in your app its worth taking a look at the Singleton design pattern. Here's an excellent summary.
You can also use the [NSUserDefaults standardUserDefaults] which is a predefined Singleton object or simply create your own.

Related

Pass data between 2 views without segues

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

Pass data between two ViewControllers [duplicate]

This question already has answers here:
Passing data between view controllers
(45 answers)
Closed 7 years ago.
I need to pass data between two ViewControllers but without UIButton, in a few words, I need to access a variable which is in other ViewController.
My code is:
LoginViewController *lvc;
NSString name=lvc.name;
This specific case might be a little easier than delegates.
From what I see, you're trying to pass login credentials (name/login/password/something). I would use two things depending on the actual matter here.
Either NSUserDefaults or -performSegueWithIdentifier:
NSUserDefaults is a file that is loaded in every app that you can read and edit, simply using the following commands :
Setting a variable :
NSString *aName;
[[NSUserDefaults standardUserDefaults]setObject:aName forKey:#"userName"];
Getting a variable :
NSString *aName = [[NSUserDefaults standardUserDefaults]objectForKey:#"userName"];
Note that you can save the following objects NSDictionary, NSArray, NSString, NSNumber, NSData, and probably a couple that I'm forgetting but someone can edit if I do.
Note that this file is loaded at every startup, so you don't wanna use that as a database but more of a small-sized storage easy to use, like for user name, preferences/settings, and stuff like that.
The other way is using performsegue between two controllers, but that requires storyboards.
Drag a segue between two of your controllers, name it (for example) fromLoginToHome. I'm assuming that the flow goes from the login controller to the home controller.
when you move between the two views (when the user presses "Login" for example), call this method
[self performSegueWithidentifier:#"fromLoginToHome" sender:self];
Then you'll need to implement this method, that is usually there but in a comment block (it's always like that when you create your Vc)
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if([segue.identifier isEqualToString:#"fromLoginToHome"]){
HomeViewController *vc = (HomeViewController*)segue.destinationViewController;
vc.myName = _myName;
}
}
Xcode using delegate to pass data between controllers This is for child to parent by usuing delegates
And For parent to child,you can use segues simply.
HTH!enjoy Coding.
You can have a look of delegate method in here delegate. can you tell me if you are looking for delegate or not
Try using as below
FirstViewController.h
#interface FirstViewController: UIViewController
- (void)GetItNow;
FirstViewController.m
- (void)GetItNow{
NSLog(#"I acheived"); }
- (IBAction)goToSecondView:(id)sender {
SecondViewController* Second= [[SecondViewControlleralloc] initWithNibName:#"SecondViewController" bundle:nil];
rqVC.addId = self.addId;
[self.view addSubview:Second.view];
}
SecondViewController.h
#property (nonatomic, assign) id delegate;
SecondViewController.m
- (IBAction)Action_LoadFunds:(id)sender {
[self.view removeFromSuperview];
[_delegate GetItNow];
}

Get variable (location information) from other class iOS

in an "OfferViewController.m" I have a locationManager function. In the locationManager function I get the currentLatitude and the currentLongitude. Now in the "OfferDetailViewController.m" I want to send the location via JS to a website.
My problem ist, that in the "OfferDetailViewController.m" I donĀ“t get the location information from the "OfferViewController.m"
Hope somebody can help me... here is my code. Thanks!
OfferViewController.m:
#interface OfferViewController ()
#end
...
#pragma mark -
#pragma mark CLLocationManagerDelegate
-(void)locationManager:(CLLocationManager *)manager
didUpdateToLocation:(CLLocation *)newLocation
fromLocation:(CLLocation *)oldLocation
{
NSString *currentLatitude = [[NSString alloc]
initWithFormat:#"%+.6f",
newLocation.coordinate.latitude];
//NSLog(#"locationManager - latitude: %#", currentLatitude);
NSString *currentLongitude = [[NSString alloc]
initWithFormat:#"%+.6f",
newLocation.coordinate.longitude];
//NSLog(#"locationManager - longitude: %#", currentLongitude);
OfferDetailViewController *offerDetailViewController = [[OfferDetailViewController alloc] init];
offerDetailViewController.longTest = currentLongitude;
offerDetailViewController.latTest = currentLatitude;
}
#end
OfferDetailViewController.m:
#interface OfferDetailViewController ()
#end
...
#synthesize latTest;
#synthesize longTest;
...
- (void)webViewDidFinishLoad:(UIWebView *)offerUrl {
NSLog(#"webViewDidFinishLoad");
if ([[self.offerUrl stringByEvaluatingJavaScriptFromString:#"document.readyState"] isEqualToString:#"complete"]) {
// UIWebView object has fully loaded.
NSLog(#"UIWebView object has fully loaded.");
NSLog(#"webViewDidFinishLoad - longTest: %#", longTest);
NSLog(#"webViewDidFinishLoad - latTest: %#", latTest);
...
}
}
Since you are dealing with two view controllers, it is recommended to use segues. Xcode's storyboard files allow you to use segues visually, with a little programmatic assistance.
Steps
Here are a few steps to accomplishing what is asked in the question. These will be discussed below in the rest of the answer.
Create a segue in storyboard
Name the segue in storyboard
Create a prepareForSegue: method & set the variables in the receiving view controller
Making a segue in storyboard
First, I'd recommend linking the two view controllers via one of these segues. To do this, go to your storyboard and have a look at your starting view controller (in this case, it's OfferViewController). Locate the button or cell (some UI element) that will trigger the transition. Then hit the "control" button on your keyboard and click on that UI element. A blue line should appear. Drag that line across to the view controller that you want to move to (in your case OfferDetailViewController), and release. Then, from the options that are displayed, choose which transition type you prefer (typically, if the view is a detail view, you want to choose "push", but unless you have a UINavigationController setup, this will throw an error. Choose "modal" if you have no idea what I just said there). Your end result should look like this:
Naming a segue in storyboard
Next, you want to name your segue so you can access it programmatically. Go ahead and click on the center icon of the segue. Ihe right side bar in Xcode should flip to a view that looks something like this:
Now, in the Identifier box, type in your segue name. This can be anything you want, but remember what it is because we will use it later. I'd also recommend outlining a general pattern for segue identification, so that if you have many segues, you don't have to keep referencing the storyboard, but for just getting something started, that's not necessary to stress over.
Setting the variables in the receiving view controller
This bit we've got to do programmatically. Apple has a handy function that is defined in UIViewController (thus, can be accessed from any UIViewController). The name if this function is 'prepareForSegue:'. Let's take advantage of that.
# pragma mark - SEGUE PREPARATION
/**
* This method prepares to transition from THIS contoller to another controller that will
* display the data in more detail.
*/
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
/* Verify this is indeed the segue you want; replace "YourSegueName" to whatever you
named your segue. */
if ([[segue identifier] isEqualToString:#"YourSegueName"]) {
OfferDetailViewController *receiverController = (OfferDetailViewController *)[segue destinationViewController];
/* here you can set whatever variables you want in 'receiverController'. These
will be accessible in the viewDidLoad: method. An example is given below. */
[receiverController setWhateverVariable:valueOfWhateverVariable];
}
}
And that's it! The data should be transferred to your new view controller, and you should be good to go!
You can store those double (or strings) values (Lat & Long) in your NSUserDefaults and grab them from your Detail View controller.
The location manager just has to be called before the detail view is shown. Make a key that you can reuse and whenever you re-save your user's new coordinates it will over-write the last ones.
ex.
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
NSString *latitudeKey = #"somelatitudekey";
NSString *longKey = #"..";
[userDefaults setObject:currentLatitude forKey:latitudeKey];
[userDefaults setObject:currentLongitude forKey:longKey];
and to retrieve...
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
NSString *latitudeKey = #"somelatitudekey";
NSString *longKey = #"..";
Double latitudeOnNextView = [userDefaults objectForKey:#"latitudeKey"];
Double longitdeOnNextView = [userDefaults objectForKey:#"longKey"];
Be sure to only store the coordinates (double or string whichever you prefer) into the user defaults - it wont work if you try and save the entire location object that the location manager returns.. unless you serialize it.
Also, when you call for the coordinates in the detail view they very well could be null if the location manager hasn't finished updating the user's location by the time you save them into the user defaults so check for those nils accordingly. **This is most likely the issue you are having now -- when you call your new view controller those values you are passing aren't there yet.

iOS SDK: about using properties when creating objects programmatically?

okay below is a standard example of creating a datepicker
- (void)viewDidLoad {
CGRect pickerFrame = CGRectMake(0,250,100,100);
UIDatePicker *myPicker = [[UIDatePicker alloc] initWithFrame:pickerFrame];
[myPicker addTarget:self action:#selector(pickerChanged:) forControlEvents:UIControlEventValueChanged];
[self.view addSubview:myPicker];
[myPicker release];
}
- (void)pickerChanged:(id)sender
{
NSLog(#"value: %#",[sender date]);
}
this is all good and well. I'm a little used to creating elements in IB so when I create an object programatically I'm not sure how to access the data.
What I mean is.. should I assign myPicker to a class property and then access it as _myPicker?
Or lets say I want to access the date inside of the pickerChanged method without calling another method. Should I assign an NSDate property and re-assign it every time the picker is changed?
I ran into some memory issues when I was trying to do it that way. I had another method grabbing _theDate, and it probably tried to access it at the same time pickerChanged was modifying it?
Anyway, what I'm getting at is "whats the proper workflow when creating things like action sheets, and pickers programmatically". When these things are changed, how should the resulting data be saved so the rest of the class can access it?
Bonus question:
Is there a difference between this?
for(UILabel *myLabel in view.subviews){
NSLog(myLabel.text);
}
and this? Do I need to check the class all the time if i know my view only contains a certain kind of object?
for((id) myLabel in view.subviews){
if([myLabel isKindOfClass:[UILabel class]){
UILabel *theLabel = myLabel;
NSLog(myLabel.text);
}
}
Generally, you will just define properties if you'll need to access them more than once. You can do this in the .m file's interface:
#interface MyObject()
#property (weak, nonatomic) UIDatePicker *myPicker;
#end
You will then be able to access it by either _myPicker or self.myPicker.
You shouldn't need another NSDate property in your class because you can access the set date at any time:
_myPicker.date
For your last question: the latter of the two is merely extra sanity checks. While you're writing your own code, and you should know what subviews you're adding in, it can't hurt to double check the type of the subviews incase anything should go wrong and you try to access selectors that don't exist. This is a larger programming question though and not necessarily objective-c or iOS specific.
The documented approach is to intercept the UIControlEventValueChanged event, as per your example.
You would then typically copy the [sender date] value to a property in your pickerChanged: method.
If the user hits a save button, then the object that presented the view containing the picker should be able to retrieve the selected date via the property.
It's not considered good practice to use isKindOfClass:. You should structure your code such that you always know what class you're dealing with.
Also, you should really switch to ARC so you don't need to worry about calling release
You need to declare a UIDatePicker property to hold one instance of your child controller
This is what you need to add in your .h file:
#property (strong, nonatomic) UIDatePicker *myPicker;
And then in your .m file you need to add a data source method for this date picker. something like what rdelmar has instructed above:
self.myPicker = [[UIDatePicker alloc] init];

Help using a view controllers methods before the view is loaded

I have a UIViewController called DebugViewController that contains a UITextView, and a public method called debugPrint which is used to write an NSString into the UITextView and display it.
Is it possible to write into the UITextView before I open the UIViewController, so that when I open it, the text previously written into it is displayed?
In my parent view controllers viewDidLoad method, I'm calling initWithNibName on the DebugViewController as follows
debugViewController = [[DebugViewController alloc] initWithNibName:#"DebugView" bundle:nil];
I then call debugPrint as follows
[debugViewController debugPrint:#"viewDidLoad"];
And some time later I call the following to open the debugViewController
debugViewController.delegate = self;
debugViewController.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
[self presentModalViewController:debugViewController animated:YES];
However all the text previously written is missing.
Please let me know how I can use a view controllers methods before the view controller displayed to the user.
Thanks,
JustinP
What you are doing is a little non-standard. The danger with that as always is that if you don't really have an expert grasp on what you're doing, you can quickly find yourself in difficulty.
If you want something set before the view is displayed to the user, then the best way to do that is to do it in the viewWillAppear method. Put it there rather than in viewDidLoad because a view might loaded once but appear many times. Where you place it depends on whether the data changes from appearance to appearance.
So, if your data is pretty static and won't change, use the viewDidLoad method.
Assuming that you'll go for the viewWillAppear option, let's do the first step by having an ivar in the view controller:
NSString *myText;
set that after init:
debugViewController = [[DebugViewController alloc] initWithNibName:#"DebugView" bundle:nil];
debugViewController.myText = #"My text here";
then, in debugViewController's viewWillAppear method:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
myTextView.text = myText;
}
The view controller life cycle is complex as you can see from the View Controller Programming Guide for iOS. So I'd say best not stray from the path of least resistance unless you have good reason. That said sometimes the best way to learn is by experimentation.

Resources