iPhone - passing data from child to parent in navigation stack - iphone

Dear fellow programmers,
What is the best way to pass information from a child in a navigation stack up one or more levels? I have searched online and can't seem to find the best way to begin implementing this.
Specifically this is my model: I have a grouped table view with several rows. When a user clicks on a row I take them to an MVC .ie click on 'select date' row takes them to a MVC with a datepicker setup (the parent passes data to the child in the process). Once this date picker (or other piece of data) has been selected, how do I pass the data back? Heres some code of the setup if it helps:
/* following is inside didSelectRowAtIndexPath of parent in navigation stack */
if (row == 1) {
DateViewController *datevc = [[DateViewController alloc] init];
datevc.selectedDate = [self dateToEnter];
[self.navigationController pushViewController:datevc animated:YES;
[datevc release];
}
DateViewController.h
#import <UIKit/UIKit.h>
#interface EnterProcedureDateViewController : UIViewController {
IBOutlet UIDatePicker *datePicker;
NSDate *selectedDate;
}
#property (nonatomic, retain) IBOutlet UIDatePicker *datePicker;
#property (nonatomic, retain) NSDate *selectedDate;
-(IBAction)selectButtonPressed:(id)sender;
#end
DateViewController.m
#import "DateViewController.h"
#implementation EnterProcedureDateViewController
#synthesize datePicker;
#synthesize selectedDate;
-(IBAction)selectButtonPressed:(id)sender {
/* how do I pass the date pickers date back to the ivar dateToEnter of parent?? */
[self.navigationController popViewControllerAnimated:YES];
}
-(void)viewWillAppear:(BOOL)animated {
if (selectedDate == nil) {
datePicker.date = [NSDate date];
} else {
datePicker.date = selectedDate;
}
[super viewWillAppear:animated];
}
...
#end
Thanks in advance as always
Andy

UINavigationController has a property, viewControllers, which returns an NSArray of all the view controllers in the stack.
The "parent" view controller (i.e. the one below your currently visible one) can be obtained by:
TableViewController *parentViewController = (TableViewController*)[self.navigationController.viewControllers objectAtIndex:self.navigationController.viewControllers.count - 2];
You can then call any methods or set properties as required.

In call backs, one of the best and recommended ways to do it is Delegates.
In your case, you will need to create a DataViewControllerDelegate and put here all the methods that Delegates of this class can call. Now, the parent should implement this delegate and register itself as the delegate when initializing it.
It's better with an example, take a look at Jonathan's answer on making custom delegates.

Something like this?
I can't see all of your code structure, so I've made some assumptions about inheritance and class structure.
Get's the view of the parent view controller, casts it to the appropriate subclass of UIView and passes a message to it.
[((MayNeedCastToSpecificClass*)[self.view parentViewController].view) setMyDate:date];

in your parentviewcontroller add the property
#property (nonatomic, retain) NSDate *selectedDate;
in childViewController after selecting the date
parentViewController *parentViewController =[self.navigationController.viewControllers objectAtIndex:self.navigationController.viewControllers.count - 2];
parentViewController.selectedDate = self.selectedDate;
[self.navigationController popViewControllerAnimated:YES];
then you can access the property from parentviewcontroller

Related

How to access values from a different UIViewController

How can I access the value from an inputField located in a second viewController?
The class name of the second view controller is SettingsViewController and the outlet name for the inputField is setRateInput.
I tried this but it didn't work…
double taxRateFromInput = [[self.settings.setRateInput text]doubleValue];
when I NSLog it comes out as The value is: (null)
Any idea what I'm doing wrong?
Here is the implementation file for the main viewController:
#import "SettingsViewController.h"
#interface ViewController ()
#property (strong, nonatomic) SettingsViewController * settings;
#end
#implementation ViewController
// lazy instantiation
-( SettingsViewController *) settings
{
if (_settings == nil) {
_settings = [[SettingsViewController alloc]init];
}
return _settings;
}
- (IBAction)calculatePrice:(id)sender {
double taxRateFromInput = [[self.settings.setRateInput text]doubleValue];
#end
In theory, you could create a global. Create a new class, call it something like taxRate (.h and .m)
In taxRate.h, add the following code:
#import <Foundation/Foundation.h>
#class MyTaxRate;
#interface TaxRate : NSObject {
}
#property (nonatomic, retain) double * taxRateFromInput;
+(TaxRate*)getInstance;
#end
Then, in your controller, put a "#import taxRate.h" in there. In your .m file, add the following:
#import "TaxRate.h"
#implementation TaxRate
#synthesize taxRateFromInput;
static TaxRate *instance =nil;
+(TaxRate *)getInstance
{
#synchronized(self)
{
if(instance==nil)
{
instance= [TaxRate new];
}
}
return instance;
}
#end
Note: This is extremely similar in structure to what I'm purposing.
if you have the reference from the object view controller you can just access by the property from your attribute.
You instantiated a new SettingsViewController, but you didn't do anything to instantiate its textfield setRateInput. You can do it when you instantiate it:
_settings = [[SettingsViewController alloc]init];
_settings.setRateInput = [UITextField alloc] initWithFrame:someFrame]];
or, as a beter solution, instantiate the text field in -init of SettingsViewController
- init {
if (self = [super init] {
self.setRateInput = [UITextField alloc] initWithFrame:someFrame]];
}
return self;
}
If you use nib files, this would be a lot easier.
Note: setRateInput is a bad name for a property. Consider rateTextField instead.
Edit I forgot to add that you have to add the text field as a subview to its parent view.
So it will be like,
_settings = [[SettingsViewController alloc]init];
_settings.setRateInput = [[UITextField alloc] initWithFrame:someFrame] autorelease];
[_settings.view addSubView:_settings.setRateInput];
In this case, the setRateInput is retained by its super view. You're not using ARC, so you can call autorelease on your text field.
The better solution: Use - (void) loadView; inside SettingsViewController. Loading the view is the responsibility of the correspondent view controller.
- (void) loadView {
self.setRateInput = [[UITextField alloc] initWithFrame:someFrame] autorelease];
[self.view addSubView:_settings.setRateInput];
}
Edit: xib files and storyboards can help you out. Give these tutorials a try.
You are on the right track, also well done with your lazy instantiation (as
a demonstration that you grasped the concept, I mean).
But note, that outlets don't get connected until viewDidLoad is called. So if you
just alloc/init your viewController (lazily), the outlet to your textfield is pointing to nil.
The outlet doesnt get connected until your controller's view property is accessed, ie the view is displayed.
What you could do is give the settings viewController a handle to your calculating viewController and let it set a public property on the calculating viewController that represents the rate.
This is a common pattern - delegation - where one viewController (settingsViewcontroller) calls a method on its delegate (calculating viewController).
You wouldn't need the settingsViewcontroller property in your calculating viewController then, but just instantiate a new settings viewController every time you want it to be brought up, giving it a reference to your calculating viewController.
Another possibility - maybe even better - is to define a model object that does calculation and takes care of the rate it needs to calculate. Then you could give your settingsViewcontroller a reference to that model object (probably instantiated in your
other viewController), so that it can change the rate on it.
PS: also re think how you instantiate viewControllers generally. The designated initialiser is -initWithNibName:bundle: - so usually, you wouldn't just alloc/ -init them.
If you use storyboards (you probably should!), use storyboard's -instantiateViewControllerWithIdentifier: or use the above mentioned designated initialiser.

Accessing viewController properties from the app delegate

I am quite new to iOS programming so please be nice :) I am trying to google out this for hours now with no success. I have setup an iOS master detail project.
What i need to do. is to change a label in the detailViewController when the app calls applicationDidEnterBackground
This is my faulty code in the appdelegate applicationDidEnterBackground method
UIViewController *temp = [self.navigationController visibleViewController];
NSLog(#"%#",[temp nibName]);
if ([temp nibName] == #"DetailViewController") {
temp._lblBrewingTime = #"";
}
This doesnt work. semantic issue: lblbrewingtime not found on object of type UIViewController.
If I add a breakpoint and check the structure of the temp pointer. I can see the _lblBrewingTime type.
Can you please point me how to get the properties of whatever view is currently loaded in the app delegate?
thank you very much,
Greets,
Nick
You have to explicitly cast it to DetailViewController, once you are sure that the visibleViewController is DetailViewController actually.
So here's the fix:-
UIViewController *temp = [self.navigationController visibleViewController];
NSLog(#"%#",[temp nibName]);
if ([temp nibName] == #"DetailViewController") {
DetailViewController* tempDVCObj = (DetailViewController*)temp;
//temp._lblBrewingTime = #"";
tempDVCObj._lblBrewingTime = #"";
}
And it says absolutely correct that your property _lblBrewingTime is not the property of UIViewController, it's the property of DetailViewController i.e. a subclass of UIViewController.
Some things here:
You should keep a reference to your main controller in the AppDelegate and access the view through this reference - the visible view controller in the navigation controller may not be your view controller class, e.g. because you navigated to another view.
You access the view controller via the UIViewController interface. The UIViewController class does not know about your child view controller's properties, so it cannot access the _lblBrewingType. You have to use your view controller's class name to access its properties, e.g. MyViewController * myVc = (MyViewController*)viewController.
_lblBrewingType looks like an internal variable of your view controller. To access it from the outside, you must provide it as a property:
// MyViewController.h
#interface MyViewController : UIViewController
{
UILabel* _lblBrewingType;
}
#property (strong, nonatomic) IBOutlet UILabel *lblBrewingType;
And the implementation:
// MyViewController.m
#implementation MyViewController
#synthesize lblBrewingType;
#end

Accessing variable values from one view in another

I'm having some trouble understanding how variable values are passed from one view to another. I have a UITextField in the firstview that the user enters a number into. When the user taps a button, that number is multiplied by 2 and the result is displayed on a UILabel in the second view. This is what I have thus far
FirstViewController.h
#interface FirstViewController : UIViewController{
UITextField *numberTextField;
NSNumber *aNumber;
}
#property (nonatomic, retain) IBOutlet UITextField *numberTextField;
#property (nonatomic) NSNumber *aNumber;
-(IBAction)calculate;
#end
FirstViewController.m
#implementation FirstViewController
#synthesize numberTextField, aNumber;
-(double)doubleNumber{
double number = [numberTextField.text doubleValue] * 2;
return number;
}
-(IBAction)calculate{
self.aNumber = [NSNumber numberWithDouble:[self doubleNumber]];
}
//more default code continues below
SecondViewController.h
#import "FirstViewController.h"
#interface SecondViewController : FirstViewController{
UILabel *numberLabel;
}
#property (nonatomic, retain) IBOutlet UILabel *numberLabel;
#end
SecondViewController.m
#implementation SecondViewController
#synthesize numberLabel;
- (void)viewDidLoad
{
[super viewDidLoad];
numberLabel.text = [NSString stringWithFormat:#"%#",aNumber];
}
Best and Easy Way to store value globally
set you object with your keyword
[[NSUserDefaults standardUserDefaults] setObject:#"Ajay" forKey:#"name"];
than get that object any where in you project
NSString *name = [[NSUserDefaults standardUserDefaults] objectForKey:#"name"];
You should accomplish what you want by using a segue. Create a segue in your storyboard and then call it in your firstviewcontroller with - (void)performSegueWithIdentifier:(NSString *)identifier sender:(id)sender. Then to pass data, import your secondviewcontroller.h file with a property for the value you want to pass and setup - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender.
In that method you can pass things by using [segue.destinationViewController ###call the setter for the property in your secondViewController.h file###];
If this isn't clear, or you need more help just let me know.
I'm assuming that the FirstViewController is instantiating the SecondViewController. If that is so, then you just pass aNumber to the SecondViewController using an additional property:
// add an additional property to the SecondViewController
#property (nonatomic, retain) NSNumber *aNumber;
When you instantiate the SecondViewController inside the FirstViewController, you just pass that value to the SecondViewController before you load it:
// inside FirstViewController
SecondViewController *secondViewController = [[SecondViewController alloc] initWithNibName:#"SecondViewController" bundle:nil];
secondViewController.aNumber = aNumber;
// inside SecondViewController
- (void)viewDidLoad
{
[super viewDidLoad];
numberLabel.text = self.aNumber;
}
Mmm... Pay attention to not confuse views and viewControllers!!!
A viewController can manage more than a view. For example in your code you have a UITextField, a UILabel and probably a UIButton. These are all views that are managed by two viewsController (FirstViewController and SecondViewController).
As long as you have so few views to work with you can use just one viewController and pass the value you want to your UILabel directly:
- (void)calculateAndPassValue
{
aNumber = [NSNumber numberWithDouble:[self doubleNumber]];
numberLabel.text = [NSString stringWithFormat:#"%#",aNumber];
}
Otherwise, if your goal is passing variable values from one viewController to another. Well... There are many ways you can obtain that, for example:
Creating an ivar.
Using a singleton.
Saving your data in NSUserDefault.
Creating a database on disk.
First and second cases are good if you need to manage your data while your app is running. Third and fourth if you want to memorize your data for future use and retrieve them at next start up.
Try to search keys like ivar, singleton, NSUserDefault and you'll find many discussions and lines of sample code.

pass data from mainView to a subview

I am building a utility-based application, the data is stored in the MainViewController, and now I know how to pass data to the FlipsideViewController (many regards to this thread BTW, Sending data from Mainview to Flipside?). But I am getting the data onto an subview (subclass of UIView) that I have added to the flipside view. How can I pass data to this subview? I saw there is already a delegate and protocol set up in the FlipsideViewController.h, I am really new to the delegate sort of things. Any help would be greatly appreciated!
Updates:
On the main view, I have a couple of text fields for users to input to create an object. All the objects are stored in an array. Namely, my data is created and stored in the MainViewController. Now on the flip side, I have a custom UIView subclass which allows me to do my own drawing based on the data in that array. What I need to do here is pass the data that stored in MainViewController to this subview. Here is my relevant code:
In the MainViewController.m
- (IBAction)showInfo:(id)sender {
FlipsideViewController *controller = [[FlipsideViewController alloc] initWithNibName:#"FlipsideView" bundle:nil];
controller.delegate = self;
controller.receiver = data;//this is what I've done.
controller.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
[self presentModalViewController:controller animated:YES];
[controller release];
}
In the FlipsideViewController.h
#protocol FlipsideViewControllerDelegate;
#interface FlipsideViewController : UIViewController {
id <FlipsideViewControllerDelegate> delegate;
DataModel *receiver; //create a property to receive the data transferred from main view
}
#property (nonatomic, assign) id <FlipsideViewControllerDelegate> delegate;
#property (nonatomic, retain) DataModel *receiver;
- (IBAction)done:(id)sender;
#end
#protocol FlipsideViewControllerDelegate
- (void)flipsideViewControllerDidFinish:(FlipsideViewController *)controller;
#end
In the above code, "data" is an DataModel object declared in the MainViewController.h file.
And I want to do my custom drawing in drawing class (subclass of UIView), how can I pass data from the FlipsideViewControllerto this subview? Do I need to make use of delegate declared in the FlipsideViewController.h file? Thanks in advance!
I have had a quick look at the template and think you are getting confused with what the delegate is being used for.
The delegate in this template is not transferring data. When you have clicked the done button it calls back to MainViewController and asks it to call the dismissModalViewControllerAnimated method so that it can remove the view controller. This seems a bit superflous as the documentation states
If you call this method on the modal view controller itself, however, the modal view controller automatically forwards the message to its parent view controller.
Therefore you don't really need to call the parent to do this.
In Interface builder you can see that the FlipsideView.xib has it's File's Owner set to FlipsideViewController.xib.
Now if you right click the File's Owner you will see that view is connected to View this basically means that view is the name of the property in FlipsideViewController and View is the element in Interface Builder.
Therefore we can access elements in the xib file from FlipsideViewController using outlets.
To say draw a label you will need to do a couple of things
First add a property in the .h and synthesize it in the .m like
// FlipsideViewController.h
#interface FlipsideViewController : UIViewController
#property (nonatomic, retain) IBOutlet UILabel *testLabel; // <----- Added this
#property (nonatomic, assign) id <FlipsideViewControllerDelegate> delegate;
- (IBAction)done:(id)sender;
#end
// FlipsideViewController.m
#implementation FlipsideViewController
#synthesize delegate = _delegate;
#synthesize testLabel = _testLabel; // <----- Added this
// More methods
- (void)dealloc
{
[_testLabel release]; // Always do you memory management
[super dealloc];
}
Then back in Interface Builder
Add a UILabel element to your view
ctrl + drag from File's Owner to the UILabel you added
Select the label in my example it is testLabel
Now these are hooked up correctly. The place where you want to be setting the value of the label is in viewDidLoad: which you can now do like this
- (void)viewDidLoad
{
[super viewDidLoad];
self.testLabel.text = #"It Works"; // You would use the data passed in from `MainViewController`
}
I find the easiest way to pass data from one view to another is by directly setting the data in the next view from the original view.
For example;
In your FlipsideViewController.h, declare a 'container' for the data you want to pass. It must be the same class on both sides to work properly, ie. NSArray to NSArray, NSMutableDictionary to NSMutableDictionary.
NSMutableArray *newData;
...
#property (nonatomic, retain) NSMutableArray *newData; // This allows you to access this object from outside this class.
and in FlipsideViewController.m
#synthesize newData;
...
[newData release];
Now we need to pass the data, so to speak. Let's say the data we want to 'send' is stored in a NSMutableArray called 'results'.
In our MainViewController.m, when we are instantiating our next view controller (in this case FlipsideViewController) we can directly reference the newData mutable array after we initalize the nib.
FlipsideViewController *controller = [[FlipsideViewController alloc] initWithNibName:#"FlipsideView" bundle:nil];
controller.newData = results;
controller.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
[self presentModalViewController:controller animated:YES];
[controller release];
Make sure you are importing your FlipsideViewController in your MainViewController.h file.
If the property is declared in your .h file, you can pretty much reference the contents of the object from anywhere within the view stack!
Hope that helps :D

Object-oriented design question, iPhone

Sorry I'm still a noob and just learning to program as I go and want to start out on the right foot by learning good design up front. I am using the CLLocationManager and MKReverseGecoder to get my location. In my MKReverseGecoderDelegate method, I create my annotation to show on the MKMapView. In my callout, I use a detail disclosure indicator to bring up another UITableView that displays your current address nicely as opposed to looking at the little black callout bubble.
What is a good way for my DetailViewController (the UITableView) to get the data? Do I have my first class have ivars for address, state, zipcode. In my MKReverseGecoderDelegate, set those ivars when I get that information. (The reason I would think I would need ivars is because my method to get that information in the MKReverseGeocoderDelegate is separate from the displayDetailViewController). And then do I have my DetailViewController have those same values, and when I go to display the DetailViewController, set those same variables? It seems redundant.
Any help would be greatly appreciated. Thanks!
One option
Declare custom class inheriting NSObject like
#interface YourClassName : NSObject
{
NSString *address;
NSString *state;
NSString *zipcode;
}
#property(nonatomic, retain) NSString *address;
#property(nonatomic, retain) NSString *state;
#property(nonatomic, retain) NSString *zipcode;
#end
#implementation YourClassName
#synthesize address,state,zipcode;
-(void)dealloc
{
[super dealloc];
[address release];
[state release];
[zipcode release];
}
#end
//Create object of YourClassName and set values
YourClassName *objYourClassName = [[YourClassName alloc] init];
objYourClassName.address = #"YourValue";
objYourClassName.state = #"YourValue";
objYourClassName.zipcode = #"YourValue";
Pass this object to your DetailViewController by one method after creating method like
-(void)setDetailsForDetailViewController:(YourClassName*)pObjYourClassName
{
//self.objOfYourClassName is property declared in your detailviewcontroller.
self.objOfYourClassName = pObjYourClassName; //You can use self.objOfYourClassName to set values in TableViewController.
}
If you stuck any where let me know I would be glad to help you fix that.
If you are doing the reverse geocoding on demand, initialize the DetailViewController with the coordinate of the annotation. Something like this:
- (id)initWithCoordinate:(CLLocation*)location {
if (self = [super initWithNibName:#"DetailController" bundle:nil]) {
self.location = location;
}
return self;
}
This is a common pattern to create the controllers, because it makes it clear for the controller's user that the controller depends on a location parameter. The other alternatives (global variables, or a singleton) are not so clean because they hide information and make the controller harder to understand and unit test.
Then let the controller launch an asynchronous task to do the geocoding, set itself as delegate, and present the information when it's done.