sharing Data between a UIViewController and a UIViewController in UITabBarController xcode - iphone

I know how to do for share data between 2 views. But if I want to share data using a tabBarController I'm lost.
This is my IBAction to move to my tabBar.
-(IBAction)goToPage2:(id)sender
{
tabController.modalTransitionStyle=UIModalTransitionStyleFlipHorizontal;
[self presentModalViewController:tabController animated:YES];
}
i need to share my NSString *dataStr in my IBAction in the first view of my tabBar.
firstView *first = [[firstView alloc] initWithNibName:#"firstView" bundle:nil];
first.dataStr = name.text;
[tabController presentModalViewController:first animated:YES];
this code doesn't work.
thx

Declare a #property in your app delegate. And you can access your app delegate from any point of your app.
MyAppDelegate *appDelegate = (MyAppDelegate *)[[UIApplication sharedApplication ]delegate]

I agree with what Terente suggested. But I recommend you should use a Data Class in this case. Using a property in Appdelegate is not a good practice. You should always use Data Class for this.
You create a Data class as follows:
You need to create a Data class where you can set the properties of variables or in your case arrays (for displaying data in UITableView). Implement a class method in data class which checks that object has been instantiated or not. If not, it does that. It is something like this :
//DataClass.h
#interface DataClass : NSObject {
NSMutableArray *nameArray;
NSMutableArray *placeArray;
}
#property(nonatomic,retain)NSMutableArray *nameArray;
#property(nonatomic,retain)NSMutableArray *placeArray;
+(DataClass*)getInstance;
#end
//DataClass.m
#implementation DataClass
#synthesize nameArray;
#synthesize placeArray;
static DataClass *instance =nil;
+(DataClass *)getInstance
{
#synchronized(self)
{
if(instance==nil)
{
instance= [DataClass new];
}
}
return instance;
}
Now in your view controller you need to call this method as :
DataClass *obj=[DataClass getInstance];
And use the arrays.
This way you can assign data without disturbing AppDelegate, which is a good practice.

I wrote a lengthy tutorial about such issues on my blog: http://www.hollance.com/2011/04/making-your-classes-talk-to-each-other-part-1/
You need to figure out a clean way to let your controllers communicate with each other. My tutorial explains several ways to do this and what the advantages and downsides are of each approach. It's worth learning how to do this, because this issue comes up in almost any app you will write.

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

Can i have a single NSMutableArray in my multiple views application?

I have a navigational based application which has multiple views. Is it possible to use one single NSMutableArray for the whole applicaiton? Can i add objects to that NSMutableArray in one view and then remove object from the same NSMutableArray from some other view? I tried
myappAppDelegate *appDelegate = (myappAppDelegate *)[[UIApplication sharedApplication] delegate];
but it gives me null when i try to access appDelegate's array. If anyone can give me any idea or helping link or tutrorial. Thanks in advance.
If you are having multiple views in your application, and in that case you want to have a variable accessible to every view, you should always create a Model/Data(singleton) class and define the variable in it. Something like this :
//DataClass.h
#interface DataClass : NSObject {
NSMutableArray *arrGlobal;
}
#property(nonatomic,retain)NSMutableArray *arrGlobal;
+(DataClass*)getInstance;
#end
//DataClass.m
#implementation DataClass
#synthesize arrGlobal;
static DataClass *instance =nil;
+(DataClass *)getInstance
{
#synchronized(self)
{
if(instance==nil)
{
instance= [DataClass new];
}
}
return instance;
}
Now in your view controller you need to call this method as :
DataClass *obj=[DataClass getInstance];
obj.arrGlobal = arrLocal;
This variable will be accessible to every view controller. You just have to create an instance of Data class.
For your type of issue I would use a singleton.
http://en.wikipedia.org/wiki/Singleton_pattern
The appdelegate is a singleton too but you can reduce a bit the number of coded lines if you use your own singleton.
The AppDelegate approach should work, and you should probably figure out why it's not working, even if you go with a singleton.
The statement to get your appDelegate pointer appears to be correct, so I'm guessing that the pointer to the array is either not getting set (and retained) in your myappDelegate class, or you did not create the AppDelegate instance correctly in the first place.
On the Singleton approach add this
instance.arrGlobal = [[NSMutableArray alloc] init];
this way:
#synchronized(self)
{
if(instance==nil)
{
instance= [DataClass new];
instance.arrGlobal = [[NSMutableArray alloc] init];
}
}
return instance;
This way you can initilize the array and use it properly.

Passing contents of array from one class to another

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.

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;