Sharing data/string with a singleton between views - iphone

I'm trying to share a string between two views on my iPhone project. It currently works if I use the actual #"something here" for the string, but if I want to use something like label.text, it doesn't even though it is still a string.
I'll show you what I have to make it clearer.
First View: Info_ViewController.h
#import <UIKit/UIKit.h>
#interface Info_ViewController : UIViewController {
IBOutlet UITextField *locationField;
}
#property (nonatomic, retain) NSString *locationString;
+ (id)sharedInfoVC;
#end
First View: Info_ViewController.m
#import "Info_ViewController.h"
static Info_ViewController *sharedInfoVC = nil;
#implementation Info_ViewController
#synthesize locationString;
#pragma mark Singleton Methods
+ (id)sharedInfoVC {
#synchronized(self) {
if (sharedInfoVC == nil)
sharedInfoVC = [[self alloc] init];
}
return sharedInfoVC;
}
- (id)init {
if (self = [super init]) {
locationString = [[NSString alloc] initWithString:locationField.text]; //This is there part I mentioned earlier, when using #"something" instead of locationField.text works.
}
return self;
}
Second View: Confirm_ViewController.m
#import "Confirm_ViewController.h"
#import "Info_ViewController.h"
#implementation Confirm_ViewController
- (IBAction)buttonZ:(id)sender
{
Info_ViewController *infoVCmanager = [Info_ViewController sharedInfoVC];
locationLabel.text = infoVCmanager.locationString;
}
I put it under a button for now, but it will eventually be under viewDidLoad.
If you replace locationField.text with a string (#"blahblahblah") it won't crash and works.
When it crashes I get the error: Program received signal: "SIGABRT"
EDIT: I tried changing
initWithString:locationField.text
to
initWithFormat:#"%#",locationField.text
and now it my label in the second view prints "(NULL)"
Thanks for taking the time to give advice, I really appreciate it.

It is an error to pass nil as the format string to -[NSString initWithString].
So how are you passing nil? You actually have two instances of Info_ViewController. You have the one instance which is the normal part of your app, and then you also have a second instance which is your "singleton" (which really isn't a singleton any more).
So in your "singleton" instance, the UITextField is nil (and will always be nil) and so locationField.text is nil and you are passing that to initWithString:, which is a crash. In fact the "singleton" isn't even fully baked as view controller's go.
If you want a singleton to share data elsewhere in your app, it really should not be a Info_ViewController or any type of view controller. It should be of some other class that you use to manage your data. I would create another class and implement that as a singleton.
Hope that helps you understand what's happening here.

Pre-pend "self." to your location string.
self.locationString = [[NSString alloc] initWithString:locationField.text];

From what I understand of your code, you have got the value for locationString when you from the textfield when you initialize the viewController. At this point of time, your textfield would not be visible. After it becomes visible and you enter something, you don't have the code to store it to locationString.
What you should do is wait for Info_ViewController object to be initialized and displayed. Then on the press of some button or some other event, assign locationLabel.text from the locationString or even directly from locationField.text.
I would provide code, but I have no clue as to how you are structuring this. If you still need help, please provide the details.

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.

Using Custom Subclass of NS Object in 2 different IBActions

This is an objective-c problem. I have created a subclass person of NSObject with parameters 'height' and 'weight', with property and synthesize in a file called Person.h that contains both interface and implementation.
I want to import Person.h into my viewcontroller.m and create person objects and alter them using 2 IBActions.
-(IBAction)alterperson_1{
person *bob = [person alloc]init];
bob.height = 72;
bob.weight = 200;
}
-(IBAction)alterperson_2{
bob.height = 80;
bob.weight = 250;
}
This arrangement does not work because the method alterperson_2 can't find Bob because it is a local variable to alterperson_1. My question is how and where in viewcontroller.m do I allocate Bob as a person so that his attributes can be altered by both IBActions.
I have tried allocing in viewdidload as well as in the initwith nibname methods. It did not work. I have also tried in the implementation{ } of viewcontroller.m but that doesn't work either because Bob's allocation is not a compile time constant.
Thanks!
Update With Code
So, I have the Person.h file importing properly now (thanks Robotnik), and am able to create instances of Person throughout ViewController.m -- however, my created instance *bob does not seem to retain values for its properties (see comments by NSLog statements in the code). I think this is an initialization issue, but I have no idea where to initialize. Currently, I get a warning when initializing in viewDidLoad. How do I get bob.weight to print 200 when my IBAction is called, instead of the 0 I currently get? Thanks.
// Person.h
#import <Foundation/Foundation.h>
#interface Person : NSObject{
int weight;
int height;
}
#property int weight, height;
#end
end Person.h
//Person.m
#import "Person.h"
#implementation Person
#synthesize weight, height;
#end
end Person.m
//ViewController.h
#import <UIKit/UIKit.h>
#import "person.h"
#interface ViewController : UIViewController{
}
#property Person *bob;
-(IBAction)persontest:(id)sender;
#end
end ViewController.h
//ViewController.m
#import "ViewController.h"
#implementation ViewController
#synthesize bob;
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
Person *bob = [[Person alloc]init]; // this causes a local declaration warning, if I remove this code, however, it still doesn't work
bob.weight = 100;
NSLog(#"viewDidLoad bob's weight, %i", bob.weight); // this will print 100, but only because I made the local initialization. The value is lost once the viewDidLoad Method ends.
}
-(IBAction)persontest:(id)sender{
bob.weight = bob.weight + 100;
NSLog(#"IBAction bob's weight %i", bob.weight); // this prints 0, probably because value is nil. How can I make it print 200?
}
- (void)viewDidUnload
{
[super viewDidUnload];
// Release any retained subviews of the main view.
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
}
#end
end ViewController.m
You'll need to declare Bob in your ViewController.h if you want him to be accessible across multiple methods. You can then initialise him in viewDidLoad
#interface ViewController
{
Person *bob;
}
-(IBAction)alterperson_1;
-(IBAction)alterperson_2;
#end
You mentioned you wanted to instantiate multiple people. In that case you may want to keep multiple Person objects in an NSMutableArray or similar. This should still be declared in the ViewController.h in order to be accessible in multiple methods. You can then use the method addObject: to add people to the array. Here is an example of an array, storing strings.
NSMutableArray *stringArray = [[NSMutableArray alloc] init];
[stringArray addObject:#"Dog"];
[stringArray addObject:#"Cat"];
You seem to be confusing declaration of a variable with allocation of a variable. You're also not using properties properly.
Declaration of a variable is done like this:
Person *bob;
That's it. If you put this line in your #interface section, or in {braces} at the top of your implementation section, then this becomes an instance variable, any time you use bob in the rest of your class it will know that you are talking to a person. So, in your viewDidLoad, you can now do this:
bob = [[Person alloc] init];
And it knows you are referring to the instance variable. In your current code, you have Person * in front of this line, which, as the compiler is telling you, is declaring a local variable with the name bob, and therefore hiding your instance variable, so you are not changing your instance variable at all, so there is no value in it later.
Looking at the nature of this question, and your comments, I would strongly advise reading some objective-c introductory texts before proceeding much further. You can ask questions here but SO is not really the place to learn a language - most answerers will assume that you know the language basics.

property loses its value (long but easy to understand)

In my app, I have:
car.h
#interface car : NSObject
{
NSString *model;
NSString *price;
// others atributes
}
#property(nonatomic, retain) NSString *model;
#property(nonatomic, retain) NSString *price;
...
myshop.h
#import "car.h"
#interface myshop : UIViewController...
{
car *mycar;
}
#property(nonatomic, retain) car *mycar;
...
myshop.m
...
-(void) viewDidLoad
{
...
mycar = [[car alloc] init];
}
so, I have a method that shows a popover, where I can select a car from a tableview. This popover callback a method in the myshop.m, using delegate, where I assign a value to mycar.model, and call the method doA above, all of this works fine, and shows the value of mycar.model in Output:
-(void) doA
{
NSLog(#"car = %#", mycar.model );
...
}
But... now it is the problem: I have a buttom in the myshop view. When I press this button, the action shows an alert view (there is the delegate in .h). The return of this alert calls:
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
[self doA];
...
}
And the program crash when doA tries to write mycar.model to Output. No error is showing in Output View of Xcode. In the code, it shows: Thread 1: Program received signal: EXEC_BAD_ACCESS.
I can show as many cars I want from popover view, but when press the button, and the AlertView closes, the program crashes.
Just for test, I call [self doA] in other methods that runs from another class via delegate and always the app craches.
Any idea, what is wrong?
Completing the code
Is there any error here? (this is a method of car.m)
- (void) setValues: (NSDictionary *) data
{
model = [data objectForKey:#"model"];
price = [data objectForKey:#"price"];
...
I don't initialize the properties (model, price, ...) anywhere in code.
SOLVED !!!
It was a memory management problem!
I change
model = [data objectForKey:#"model"];
by
model = [[NSString alloc] initWithFormat:#"%#", [data objectForKey:#"model"]];
I hope that I am right now! At least the app seens to work well!
Thanks a lot friends !!!
You have got a memory management bug somewhere in your code. I don't know where the bug is, it almost certainly isn't in the code you posted (it all looks perfect to me).
Unless you post more code, we can't help you fix this one.
I strongly recommend you enable ARC. It's fairly new, but nowadays it's old enough everyone should start using it.
There's a very good chance your bug will simply go away if you turn ARC on.
The code you posted looks fine, but somewhere else you must be over-releasing mycar. Profile it with the "zombies" instrument, and it will tell you where it is being released.

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.

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.