Passing contents of array from one class to another - iphone

I've got an array populating a small tableView in a DetailView class, and when the user presses a button I need the array to be sent to another View Controller, to populate a tableView there, but I'm having some difficulty getting it working. This is what I've been trying to do so far:
*DetailViewController.m*
#import "DetailViewController.h"
#import "OtherViewController.h"
-(IBAction) toCart:(id)sender {
OtherViewController *oVC = [[OtherViewController alloc] init];
oVC.shoppingList = sList;
NSLog(#"Ingredients count %d", [sList count]); //This returns a number, so the sList definitely contains values, and the method is definitely being called.
[oVC release];
}
*OtherViewController.m*
#import "OtherViewController.h"
#import "DetailViewController.h"
#synthesize shoppingList;
-(void) viewWillAppear: (BOOL)animated {
NSLog(#"list count: %d", [shoppingList count]); // This returns 0
}
sList is populated elsewhere in the class, and sList and shoppingList are both declared in their respective .h files, with #property (nonatomic, retain)...
Any help much appreciated!

As you are having taBbarcontroller, so you can proceed as follows :
Create references of your you viewControllers(which are associated with tabbar as topViewController) in your appDelegate.
otherViewController = [[tabBarController.viewControllers objectAtIndex:<tabIndex>] topViewController];
make it as #property in appDelegate so that you can access it anywhere in your app.
now,
-(IBAction) toCart:(id)sender {
//appDelegate <--- get reference to your application delegate using [[UIApplication sharedApplicaiton]delegate] do not forget to properly type cast it.
OtherViewController *oVC = [appDelegate otherViewController];
oVC.shoppingList = sList;
NSLog(#"Ingredients count %d", [sList count]);
//This returns a number, so the sList definitely contains values, and the method is definitely being called.
// [oVC release]; no need to release it...
}
//also make sure you do not initialize shoppingList of otherViewController in viewDidLoad(or any other method) of otherViewController, else it will be overwritten(lost its previous reference).
in your appDelegate's .h write
#property OtherViewController *otherViewController;
in appDelegate's.m
#synthesize otherViewController;
in appDelegates's .m (method didFinishLaunchingWithOptions: ) write
otherViewController = [[tabBarController.viewControllers objectAtIndex:<tabIndex>] topViewController];
Thanks

In toCart:, you are creating an OtherViewController and then immediately throwing it away. Whatever OtherViewController is calling -viewWillAppear, it isn't the one you're creating in toCart:. How is that object created and put on the screen? You need a pointer to it to modify it.
Better, though, would be to move your model data out of the view controllers and put it in a single ShoppingCart object. Then all your view controllers would have a reference to it (or you can make ShoppingCart a singleton if that makes sense in your program). This way, any time you change the shopping cart from anywhere, all views will correctly update without having to tell every view controller about every other view controller.

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.

How many ways to pass/share data b/w view controller

im new to IOS and Objective-C and the whole MVC paradigm and i'm stuck with the following.
I am working on (replica) Contact app, also available in iphone as build in app. i want to pass data through another view controller and the data is pass (null) :(.
My Question is, How do I transfer the data from one view to another?
As most the answers you got, passing data between one controller and another just means to assign a variable from one controller to the other one.
If you have one controller to list your contacts and another one to show a contact details and the flow is starting from the list and going to detail after selecting a contact, you may assign the contact variable (may be an object from the array that is displayed in your list) and assign it to the detail view controller just before showing this one.
- (void)goToDetailViewControllerForContact:(Contact *)c
{
ContactDetailViewController *detailVC = [[[ContactDetailViewController alloc] init] autorelease];
detailVC.contact = c;
[self.navigationController pushViewController:c animated:YES];
//[self presentModalViewController:detailVC animated:YES]; //in case you don't have a navigation controller
}
On the other hand, if you want to insert a new contact from the detail controller to the list controller, I guess the best approach would be to assign the list controller as a delegate to the detail one, so when a contact is added the delegate is notified and act as expected (insert the contact to the array and reload the table view?).
#protocol ContactDelegate <NSObject>
- (void)contactWasCreated:(Contact *)c;
// - (void)contactWasDeleted:(Contact *)c; //may be useful too...
#end
#interface ContactListViewController : UIViewController <ContactDelegate>
#property (nonatomic, retain) NSArray *contacts;
...
#end
#implementation ContactListViewController
#synthesize contacts;
...
- (void)goToDetailViewControllerForContact:(Contact *)c
{
ContactDetailViewController *detailVC = [[[ContactDetailViewController alloc] init] autorelease];
detailVC.contact = c;
detailVC.delegate = self;
[self.navigationController pushViewController:c animated:YES];
//[self presentModalViewController:detailVC animated:YES]; //in case you don't have a navigation controller
}
- (void)contactWasCreated:(Contact *)c
{
self.contacts = [self.contacts arrayByAddingObject:c]; //I'm not sure this is the correct method signature...
[self reloadContacts]; //may be [self.tableView reloadData];
}
...
#end
#interface ContactDetailViewController : UIViewController
#property (nonatomic, assign) id<ContactDelegate> delegate;
...
#end
#implementation ContactDetailViewController
#synthesize delegate; //remember to don't release it on dealloc as it is an assigned property
...
- (void)createContactAction
{
Contact *c = [[[Contact alloc] init] autorelease];
[c configure];
[self.delegate contactWasCreated:c];
}
...
#end
Technically, you shouldn't!
The whole idea is not for "views" to control what happens to the data.
What you want to do is to pass data between controllers (which I imagine is exactly what you are planning to do anyway).
You can have shared model (an instance of an object that both view controllers would access) keeping the data you want to share,
You can use notifications to pass data (it is best suited for certain cases).
You can write something to disk and read it again later.
You can use NSUserDefaults.
You can use KeyChain.
...
The best way is:
declare the appropriate #property in the second view controller
when you create it, simply set the property with
viewController.property = valueYouWantToPass;
I'm a big fan of delegates and protocols.
And in some occasions use a Singleton pattern.
two ways to pass/share data between view controller
create an object and sent the data like this
QGraduteYr *tableverify=[[QGraduteYr alloc]initWithStyle:UITableViewStyleGrouped];
tableverify.mystring=myString
[self.navigationController pushViewController:tableverify animated:YES];
another method is stor it in the delegates and use it via shared delegates
MedicalAppDelegate *appdelegate=(MedicalAppDelegate *)[[UIApplication sharedApplication]delegate];
appdelegate.collnameStr=collStr;
and ust this appdelegates value whereever you need

iPhone - UINavigationController question

I want to display some data on table, let's call it "TabeView" app.
So I created a "navigation-based" application on XCode and it gives me 4 files in the "classes folder".
RootViewController.h
RootViewController.m
TableViewAppDelegate.h
TableViewAppDelegate.m
Now, I wanted to set up the data in TableViewAppDelegate using the "didFinishLaunchingWithOptions" method, which I did using an array.
Then, I need to "send" this array to the RootViewController. I have an array variable in RootViewController so I assume that I need to "set" the array variable in RootViewController from the TableViewAppDelegate. How can I do this?
The problem I am having is, that I don't know how to set the array variable in RootViewController from TableViewAppDelegate. I want to know if something like below is possible
....
[RootViewController setMyArray:myArray];
[window addSubview:navigationController.view];
[window makeKeyAndVisible];
....
But I have no idea how to call "RootViewController".
Hopefully I made some sense. Thank you.
First, create a property in your RootViewController class for the array variable. So e.g. if your array variable is called myArray your code might look something like this:
In RootViewController.h:
#import <UIKit/UIKit.h>
#interface RootViewController : UITableViewController {
NSArray *myArray;
}
#property (nonatomic, retain) NSArray *myArray;
#end
and add the corresponding #synthesize line in RootViewController.m:
#import "RootViewController.h"
#implementation RootViewController
#synthesize myArray;
// methods here
#end
Now you can set the myArray member of a RootViewController object like this:
myRootViewController.myArray = [NSArray arrayWithObjects:#"foo", #"bar", nil];
Now, in your app delegate class, you can use the viewControllers property of self.navigationController to return the view controllers in the navigation stack. The root controller will always be at index 0. viewControllers will return an NSArray, whose elements are of type NSObject, so you need to cast to a RootViewController pointer too. Here it is in two lines, to make the cast explicit:
// get a pointer to the root view controller
id obj = [self.navigationController.viewControllers objectAtIndex:0];
// cast it to a RootViewController pointer
RootViewController* rvc = (RootViewController*)obj;
// Now you can set the array
rvc.myArray = someArray;
(In order to use the RootViewController class name in your app delegate class you'll need to import the relevant header file - put this at the top of TableViewAppDelegate.m:
#import "RootViewController.h"
And that's it!
p.s. Bear in mind that declaring your myArray property as type (nonatomic, retain) means that your RootViewController object will end up pointing to the same instance of NSArray that you pass to it. So, for example:
NSMutableArray *array = [NSMutableArray arrayWithObjects: #"foo", #"bar", nil];
myRootViewController.myArray = array;
[array removeAllObjects]; // myRootViewController.myArray is now empty!
You can use (nonatomic, copy) instead, in which case your RootViewController object will make a copy of the array you pass in and retain that instead. Changes to the array once you've assigned it to your RootViewController object won't affect the copy.
navigationController.myArray = myArray;
before
[window addSubview:navigationController.view];

iPhone: Using a NSMutableArry in the AppDelegate as a Global Variable

What i'm trying to accomplish is to have an NSMutableArray defined in the AppDelegate. I then have two UIViewControllers. One view is responsible for displaying the array from the AppDelegate. The other view is used to add items to the array. So the array starts out to be empty. View1 doesn't display anything because the array is empty. The User goes to View2 and adds an item to the array in AppDelegate. Then when the user goes back to View1 it now displays one item.
Here is how I'm trying to accomplish this
#interface CalcAppDelegate : NSObject <UIApplicationDelegate> {
UIWindow *window;
UITabBarController *tabBarController;
NSMutableArray *globalClasses;
}
#property (nonatomic,retain) NSMutableArray *globalClasses;
My other view
In the viewDidload I set the array in my View to be the one in the AppDelegate. In an effort to retain values.
allCourses = [[NSMutableArray alloc]init];
CalcAppDelegate *appDelegate = (CalcAppDelegate *)[[UIApplication sharedApplication] delegate];
allCourses = appDelegate.globalClasses;
Then I would update my allCourses array by adding a new item. Then try to set the array in the AppDelegate to be equal to the modified one.
CalcAppDelegate *appDel = (CalcAppDelegate *)[[UIApplication sharedApplication] delegate];
NSLog(#"Size before reset %d",[appDel.globalClasses count]);
appDel.globalClasses = allCourses;
NSLog(#"Size after reset %d",[appDel.globalClasses count]);
What I'm seeing that's returned is 2 in the before, and 2 after. So it doesn't appear to be getting updated properly. Any suggestions?
A few things:
First, in your app delegate you need to make sure that you intialize the array before any object tries to access it. A customer getter is good for this.
-(void) getGlobalClasses{
if (globalClasses!=nil) {
return globalClasses;
}
NSMutableArray *newArray=[[NSMutableArray alloc] initWithCapacity:1]; //yes, I'm old school
self.globalClasses=newArray;
[newArray release];
return globalClasses;
}
Now any call to the property is guaranteed to return an array.
In your view controllers, you need to define properties to hold the array. Since the array is held by the app delegate and will always be there, it is best to assign the array instead of retaining it. That way you always know you are writing to the exact same array and the app delegate has complete control over its life cycle.
In the view controllers:
#property(nonatomic,assign) NSMutableArray *globalClasses;
then every time you reference it make sure to use the self notation:
self.globalClasses=//...whatever
Having said all this, it is extremely bad practice to stick an array or any other dumb data object out their buck naked in your app. You have no control over what each piece of code will do to the array. You will have to duplicate all your validation code every place you add or remove data to the array.
It would be better to wrap the array in a custom class and make it protected so it can only be altered by the classes methods.
Like so:
#interface MyData : NSObject {
#protected
NSMutableArray *myDataArray;
}
-(void) addObject:(id) anObject;
-(void) removeObjectAtIndex;(NSInteger) anIndex;
#end
scratch.m
#interface scratch ()
#property(nonatomic, retain) NSMutableArray *myDataArray;
#end
#implementation scratch
#synthesize myDataArray;
-(void) addObject:(id) anObject{
//...code to check if anObject is a valid one to add to the array
[self.myDataArray addObject:anObject];
}//------------------------------------addObject:------------------------------------
-(void) removeObjectAtIndex;(NSInteger) anIndex{
//... do bounds checking and other testing to ensure no problems
// will result from removing the object at the given idex
[self.myDataArray removeObjectAtIndex:anIndex];
}//-------------------------------------(void) removeObjectAtIndex;(NSInteger) anIndex------------------------------------
Then add the custom class an a property of the app delegate as shown above. This will keep your data clean and modular so you can safely use it in a wide range of app reobjects without having to micromanage the array in every object.
There are a couple problems here:
allCourses = [[NSMutableArray alloc] init];
CalcAppDelegate *appDelegate = (CalcAppDelegate *)[[UIApplication sharedApplication] delegate];
allCourses = appDelegate.globalClasses;
You do not need to allocate a new array since you want to retain an existing array (the one in the app delegate)
If you're using properties, you need to use the self declaration to retain the app delegate array
Instead, try:
CalcAppDelegate *appDelegate = (CalcAppDelegate *)[[UIApplication sharedApplication] delegate];
self.allCourses = appDelegate.globalClasses;

AppDelegate: Get value from ViewController?

I would like to get at
- (void)applicationWillTerminate:(UIApplication *)application
a variable from a view controller class.
I have build an tabbar application and only added the tabbar controller to the appdelegate.
[window addSubview:tabBarController.view];
How can i get an variable from the TestViewController:
#import <UIKit/UIKit.h>
#import <AVFoundation/AVFoundation.h>
#interface TestViewController : UIViewController {
IBOutlet UILabel *testLabel;
NSString *currentString; //Value that i want to save at applicationWillTerminate
}
#property (nonatomic, retain) UILabel* testLabel;
#property (nonatomic, retain) NSString* currentString;
#end
It's somewhat incidental that TestViewController hasn't been dealloc'd by the time you reach applicationWillTerminate - it might make sense to store that value a level higher in your application. That approach would be to always store currentString in the UIApplicationDelegate so that you don't have to fetch it later:
#implementation TestViewController
- (void)setCurrentString:(NSString *)currentString {
((MyAppDelegate *)[[UIApplication sharedApplication] delegate]).currentString = currentString;
}
#end
Expanding on dbarker's answer, it sounds like what you really need is to save the currentString value in your data model. The proper place to do that is in the viewController itself.
If your data model is just that one string, you can create a property in the app delegate to hold it. Then the viewController writes to the app delegate property whenever the value of currentString changes in a view and/or its value when the view closes.
This way, the data (which is the entire point of the app anyway) is always in place when the app closes regardless of how many views you open.
It is the proper role of controllers to move information from the interface to the data model. Strictly speaking, the viewController shouldn't store any data at all beyond that needed by the interface itself. That should be a property of the data model which the viewControllers set by sending a message to the data model object with the values taken from the interface.
In this case, you would not have a currentString property in your view controllers. Instead they would have a property that is just a reference to the data model's currentString property. The view controllers would continuously update that property but would store nothing themselves.
The advantage of this design is obvious. If you need the value anywhere in your app, you have one location and one call to get it. No single part of the app needs to even know of the existence of any other part of the app save for the data model.
Not 100% sure what you are asking for but here is a guess:
UITabBarController's have a property called viewControllers which return all view controllers associated with the tabbar.
Assuming that the TestViewController was the first tab you could get to it by:
- (void)applicationWillTerminate:(UIApplication *)application {
TestViewController* test_view_controller = [tabBarController.viewControllers objectAtIndex:0] ;
NSString* value = test_view_controller.currentString ;
}
Note this would break if you decided to later move the TestViewController to a different position in the tabbar.
-- Edit --
Check all controllers and get the string from the controller that is of type TestViewController.
NSString* value = nil ;
for ( id unknownController in tabBarController.viewControllers ) {
if ( [unknownController isKindOfClass:[TestViewController class]] ) {
value = ((TestViewController*)unknownController).currentString ;
}
}
// value should be the value of the string.