I need just four parameters to drive the calculations in three view controllers. The parameters come from four UITextFields in the first view. I'm not using global variables but have developed a singleton class to enable the parameters to be available to each controller. I can reference the variables OK in the controllers but I don't know how to initialize them. I've obtained the variables from the first viewcontroller through a windows I developed with IB but can't seem to find a way to get these initialized in the singleton.
Help and guidance gratefully appreciated.
//
// GlobalParameters.h
// ProjectEstimator
//
// This is a SINGLETON class used to handle global parameters for use in the various view controllers.
//
//
//
// Created by Frank Williamson on 10/06/2010.
//
#import <Foundation/Foundation.h>
#interface GlobalParameters : NSObject {
// Place any "global" variables here
// float *processes;
// float *entities;
// float *transactions;
// float *users;
IBOutlet UITextField *noOfProcesses;
IBOutlet UITextField *noOfEntityClusters;
IBOutlet UITextField *noOfTransactions;
IBOutlet UITextField *noOfUserArea;
}
#property (retain, nonatomic) UITextField *noOfProcesses;
#property (retain, nonatomic) UITextField *noOfEntityClusters;
#property (retain, nonatomic) UITextField *noOfTransactions;
#property (retain, nonatomic) UITextField *noOfUserArea;
// message from which our instance is obtained
+ (GlobalParameters *)sharedInstance;
#end
//
// GlobalParameters.m
// ProjectEstimator Singleton for handling (global) parameters.
//
// Created by Frank Williamson on 10/06/2010.
//
#import "GlobalParameters.h"
#implementation GlobalParameters;
#synthesize noOfProcesses;
#synthesize noOfEntityClusters;
#synthesize noOfTransactions;
#synthesize noOfUserArea;
+ (GlobalParameters *)sharedInstance{
// the instance of this class is stored here
static GlobalParameters *noOfProcesses = nil;
static GlobalParameters *noOfEntityClusters = nil;
static GlobalParameters *noOfTransactions = nil;
static GlobalParameters *noOfUserArea = nil;
// check to see if an instance already exists
if (nil == noOfProcesses) {
noOfProcesses = [[[self class] alloc] init];
// **How to I initialize UITextFields from a ViewController in here??**
}
// return the instance of this class
return noOfProcesses;
if (nil == noOfEntityClusters) {
noOfEntityClusters = [[[self class] alloc] init];
// **How to I initialize UITextFields from a ViewController in here??**
}
// return the instance of this class
return noOfEntityClusters;
if (nil == noOfTransactions) {
noOfTransactions = [[[self class] alloc] init];
// **How to I initialize UITextFields from a ViewController in here??**
}
// return the instance of this class
return noOfTransactions;
if (nil == noOfUserArea) {
noOfUserArea = [[[self class] alloc] init];
// **How to I initialize UITextFields from a ViewController in here??**
}
// return the instance of this class
return noOfUserArea;
}
#end
There's a number of things here I would change. First, I wouldn't create a "Globals" class. Second, if I did, would never store UIView objects in class designed to be a "model". Third, if I absolutely needed to implement a singleton, I would follow the design pattern for Cocoa.
I need just four parameters to drive the calculations in three view controllers.
And at that point I recommend you throw out this class entirely and re-think your design problem. Is it simply that you need to store four NSString parameters (or NSNumbers or NSIntegers or floats or...)? If so, it's complete overkill to store the parameters inside a view class. Just create a simple class to store the parameters, whose header would look something like:
#interface MyParameters : NSObject
{
NSString* someString;
NSNumber* someNumber;
NSInteger someInteger;
float someFloat;
}
#property (nonatomic, retain) NSString* someString;
#property (nonatomic, retain) NSNumber* someNumber;
#property (nonatomic, assign) NSInteger someInteger;
#property (nonatomic, assign) float someFloat;
#end
I would instantiate an object of this class either in my application delegate or in my root view controller, and then I would pass it along to any other view controllers that needed access to it. Finally, I would use Key-Value Observing so every view controller that needs it will get automatic updates to any changes to the parameters.
Related
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.
I created a class AppSettings containing booleans:
#import <Foundation/Foundation.h>
#interface AppSettings : NSObject{
bool bip10secCountdown;
bool jv10secCountdown;
bool jv30secAlert;
bool jv1minAlert;
bool jvp5minAlert;
}
#property bool bip10secCountdown;
#property bool jv10secCountdown;
#property bool jv30secAlert;
#property bool jv1minAlert;
#property bool jv5minAlert;
#end
And the implementation:
#import "AppSettings.h"
#implementation AppSettings
#synthesize bip10secCountdown, jv10secCountdown, jv30secAlert, jv1minAlert, jv5minAlert;
- (id)init
{
self = [super init];
if (self){
}
return self;
}
#end
Then I'm trying to use this class in my main class but after initializing the object in the viewDidLoad, when I want to use it again it appears as null.. So I guess it's released too early. I'm using ARC so I don't manually manage the memory. Am I doing something wrong ?
The declaration in the main class:
AppSettings *appSettings;
}
#property(nonatomic)bool activated;
#property (nonatomic, strong) AppSettings *appSettings;
And the implementation:
#synthetize appSettings
...
- (void)viewDidLoad
{
// Initialize the model
self.appSettings = [[AppSettings alloc]init];
NSLog(#"appSettings = %#",self.appSettings);
The first output is OK.
But then when I try to access appSettings from another method in the Main class, the appSettings is (null)
Thank you for your help.
The view has most likely unloaded, or your view controller has been released. Try moving the initialization code to the method where your view controller is initiated(- (id)initWithNibName: bundle:)
Instead of:
#property (nonatomic, strong) AppSettings *appSettings;
Try:
#property (nonatomic, retain) AppSettings *appSettings;
And I'd recommend to make your AppSettings as a Singleton-class.
When you say you initialise the view contoller in AppDelegate I presume you mean MyViewController *theView = [[MyViewController alloc] init]; or something like that. That will be your first view and you should initialise AppSettings in viewDidLoad of MyViewController, not the AppDelegate.
demon9733 suggestion regarding making AppSettings a singleton is a good one as you will be able to easily access your settings from anywhere in the app.
Also verify whether ARC is being used for MyViewController (or whatever it might be)
I'm setting a string in a view controller called ViewController and trying to access it somewhere else. This is the code:
ViewController.h
NSString *string;
...
#property (retain) NSString *string;
ViewController.m
#synthesize string;
...
-(void)viewDidLoad {
...
string = #"Test";
}
OtherViewController.m
#import "ViewController.h"
...
-(void)viewDidLoad {
ViewController *vc;
vc = [[ViewController alloc] init];
NSLog(#"String: %#", vc.string);
}
However, the log is showing: String: (null). What am I doing incorrectly? Thanks.
The viewDidLoad of ViewController is only called when the view is loaded. The view is lazily loaded when required e.g. when a call to vc.view is made.
I'm not sure what you are trying to achieve but this certainly seems like a code smell to me.
As #Fscheidl points out you are creating a new instance and not accessing an existing instance so this may add to your problem. I still believe your main issue is that you assume viewDidLoad is being called just by creating the viewController, which is not the case
edit : it doesn't necessarily need to be an NSObject class, if you want to, you could also do this on your viewController class, just be sure to also include
-(id)init
on your header
---- end of edit
if you're trying to make a class that's accessible to another view controller, why not try NSObject instead of view controller (considering you only need to take that string value)
for instance, lets call that viewController class "global" class
so at global.h, you put up
#import <Foundation/Foundation.h>
#interface GlobalVar : NSObject
#property (nonatomic, strong) NSString *myString;
-(id)init;
#end
and then, at global.m you put up
#import "GlobalVar.h"
#implementation GlobalVar
#synthesize myString;
-(id)init
{
self = [super init];
if(self)
{
myString = [[NSString alloc]initWithFormat:#"the String"];
}
return self;
}
#end
after this, everytime you need to access the "myString" object that contained in global class, you could put up
at header :
#import "GlobalVar.h"
...
...
#property (nonatomic, strong) GlobalVar *globalVar;
at implementation file :
#synthesize globalVar;
...
...
self.globalVar = [[GlobalVar alloc]init];
NSString *theString = globalVar.myString;
NSLog(#"content of my string is : %#",theString);
there you go ;)
You do create a new instance of ViewController by calling [[ViewController alloc] init]; This instance hasn't had string even set. You have to access that exact instance of ViewController.
If you create the instance of OtherViewController directly from ViewController, you can add the following to OtherViewController.h:
#import "ViewController.h"
#property (nonatomic, retain) ViewController *previousViewController
When creating the OtherViewController, you can then set:
//alloc and init instance of OtherViewController
myOtherViewController.previousViewController = self;
In your viewDidLoad: method, you can then access your string as follows:
NSLog(#"String: %#", previousViewController.string);
I have an iPhone utility app from the standard template, so I have MainViewController and FlipsideViewController that gets initialized and called controller. In controller's xib I have a UISwitch called pathSwitch and a UISegmentedControl called locationSelector that are outlets (and hooked up!) When I call the showInfo:(id)sender method, I do the following:
[EDIT] Adding the interface of the controller...
[EDIT 2] Updated interface to show added properties
- (IBAction)showInfo:(id)sender {
ALog(#"method begin...");
FlipsideViewController *controller = [[FlipsideViewController alloc] initWithNibName:#"FlipsideView" bundle:nil];
DLog(#">sun path visible = %#, setting flip side controller switch", sunPathIsVisible ? #"YES" : #"NO");
// deleted -> [controller.pathSwitch setOn:sunPathIsVisible];
controller.sunPathIsVisible = sunPathIsVisible; // added this
DLog(#">location mode is %d, setting flip side controller segment index to %d - 1 = %d", locationMode, locationMode, locationMode - 1);
// deleted -> controller.locationSelector.selectedSegmentIndex = locationMode - 1;
controller.delegate = self;
controller.locationMode = locationMode; // added this
controller.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
[self presentModalViewController:controller animated:YES];
[controller release];
}
#interface FlipsideViewController : UIViewController {
id <FlipsideViewControllerDelegate> delegate;
int locationMode; // added this
UISegmentedControl *locationSelector;
BOOL sunPathIsVisible;
UISwitch *pathSwitch;
}
#property (nonatomic, assign) id <FlipsideViewControllerDelegate> delegate;
#property int locationMode; // added this
#property (nonatomic, retain) IBOutlet UISegmentedControl *locationSelector;
#property BOOL sunPathIsVisible; // added this
#property (nonatomic, retain) IBOutlet UISwitch *pathSwitch;;
- (IBAction)done:(id)sender;
- (IBAction)cancel:(id)sender;
#end
// There's also the `protocol` stuff, but I left that out here...
The problem is that the controls do not accept their values and always show segment 0 and OFF. If I set their properties in controller's viewWillAppear method, they do show the correct setting.
Is controller not fully loaded when I do this or something?
You're probably right. The cleanest way would be to add some properties to the FlipsideViewController, and set those. Then let viewWillAppear set the actual switches using these properties.
This will also put the UI layout issues of FlipsideViewController where they belong, namely in FlipsideViewController and not in any other controller that may ever use it. (i.e. if you ever decide to not use a switch but some kind of button, you can change FlipsideViewController without having to look at other code)
edit
Some clarification. Try to add properties to FlipsideViewController with these lines at the relevant places:
BOOL switchState;
NSInteger locationMode;
#property (nonatomic,assign) BOOL switchState;
#property (nonatomic,assign) NSInteger locationMode;
#synthesize switchState;
#synthesize locationMode;
Then, in your current -(IBAction)showInfo:(id)sender you could say:
FlipsideViewController *controller = [[FlipsideViewController alloc]
initWithNibName:#"FlipsideView" bundle:nil];
controller.switchState = sunPathIsVisible;
controller.locationMode = locationMode;
controller.delegate = self;
// etc etc
Then, in FlipsideViewController, in viewDidLoad, put the actual handling of the switch value:
[self.pathSwitch setOn:self.sunPathIsVisible];
[self.locationSelector setSelectedSegmentIndex:self.locationMode];
This will a) solve your problem and b) separate your concerns regarding the user interface. If you would decide to change the layout of FlipsideViewController, there is no need to change any code other than that of FlipsideViewController.
There are other ways of achieving this, e.g. by letting your viewDidLoad fetch the value from its delegate, which would look like:
[self.pathSwitch setOn:[delegate pathSwitch]];
Which may be better, depending on your situation. Generally speaking I would always prefer this last approach, since it prevents synchronisation issues between your different view controllers.
I am not sure but shouldn't this be
#property (nonatomic, retain) IBOutlet UISegmentedControl *locationSelector;
#property (nonatomic, retain) IBOutlet UISwitch *pathSwitch;
instead of
#property (nonatomic, assign) IBOutlet UISegmentedControl *locationSelector;
#property (nonatomic, assign) IBOutlet UISwitch *pathSwitch;;
My application is a tab bar application, with a separate view controller for each tab.
I have an object in my first view controller (A) which contains all my stored application data (Please ignore NSUserDefaults for this) which needs to be accessed by the second view controller (B) when I press a button on it. How can I achieve this in the best way?
One option you have is to declare your date model as instance variables of your app delegate (as mentioned by other commenters).
Instead of referencing the app delegate as suggested by nevan an alternative is to add a property to your view controller classes (A and B) for your data model.
Say you wanted to share a data model object between your view controllers you can add a property to each:
#interface AViewController : UIViewController {
MyDataModel *model;
}
#property (nonatomic, retain) MyDataModel *model;
#end
#interface BViewController : UIViewController {
MyDataModel *model;
}
#property (nonatomic, retain) MyDataModel *model;
#end
When you initialise your view controller you can then set this property to the object context initialised previously.
You have mentioned a tab bar controller. If your view controllers are wired through IB all you have to do is to set these parameters in your application delegate applicationDidFinishLaunching: method, before the tab bar controller is displayed:
#interface MyAppDelegate : NSObject <UIApplicationDelegate, UITabBarControllerDelegate>
{
MyDataModel *model;
AViewController *aViewController;
BViewController *bViewController;
...
}
#property (retain) IBOutlet AViewController *aViewController;
#property (retain) IBOutlet BViewController *aViewController;
#end
#implementation MyAppDelegate
...
- (void)applicationDidFinishLaunching:(UIApplication *)application
{
...
aViewController.model = model;
bViewController.model = model;
[window addSubview:tabBarController.view];
[window makeKeyAndVisible];
}
Don't forget to release the model in your view controller's dealloc method.
The alternative is to use a singleton object. An simple singleton example:
#interface MyDataModel : NSObject
{
}
+ (MyDataModel *) sharedDataModel;
#end
#implementation MyDataModel
static MyDataModel *sharedDataModel = nil;
+ (MyDataModel *) sharedDataModel
{
#synchronized(self)
{
if (sharedDataModel == nil)
{
sharedDataModel = [[MyDataModel alloc] init];
}
}
return sharedDataModel;
}
#end
You can access this data model from all your view controllers with something similar to the following:
MyDataModel *model = [MyDataModel sharedDataModel];
See also this stack overflow discussion about singletons.
The most common way I've seen this is to set up the thing you want to access in the app delegate and reference it in other places like this:
MyAppDelegate *appDelegate = (MyAppDelegate *)[[UIApplication sharedApplication] delegate];
myStuff = appDelegate.stuff;
In the app delegate, set up a stuff variable and use #property and #synthesize as usual.
Some people say that it's not a good approach, since it's the same as using global variables, but it's very common.
I like to create a top level Model class that is a singleton and contains all the elements I might need.
It's helpful to also give it a top level load method that populates objects with just the db keys, using the hydrate/dehydrate pattern common in the Apple examples.
Typical usage in the app delegate would be simply,
[[MyModel sharedModel] load];
And then in a view controller:
NSArray *myThing1s = [[MyModel sharedModel] thing1s];
NSArray *myThing2s = [[MyModel sharedModel] thing2s];
You can then iterate over your thing1s and thing2s and when you need details, you can just call
[myThing1 hydrate];
which will populate the object.
Of course, you probably want to use CoreData to manage the persistence from 3.0 onwards.
I always create a special object called DataModel and use it's singleton sharedInstance.
And this object then holds all the app-related-data. No need for accessing the dreaded appDelegate.
DataModel.h
#import <Foundation/Foundation.h>
#class MyClass1, MyClass2;
#interface DataModel : NSObject
#property (copy, nonatomic) NSString *aString;
#property (assign) BOOL aBool;
#property (strong) MyClass1 *myObject1;
#property (strong) MyClass2 *myObject2;
+ (DataModel *)sharedModel;
#end
DataModel.m
#import "DataModel.h"
#import "Class1.h"
#import "Class2.h"
#implementation DataModel
- (id) init
{
self = [super init];
if (self)
{
_myObject1 = [[MyClass1 alloc] init];
_myObject2 = [[MyClass2 alloc] init];
aBool = NO;
aString = nil;
}
return self;
}
+ (DataModel *)sharedModel
{
static DataModel *_sharedModel = nil;
static dispatch_once_t onceSecurePredicate;
dispatch_once(&onceSecurePredicate,^
{
_sharedModel = [[self alloc] init];
});
return _sharedModel;
}
#end
And (bacause I'm lazy) i put DataModel.h in application-prefix.pch.
That way i can access my data from anywhere in the application simply by calling
[DataModel sharedModel]
Both view controllers should reference a third object (C) as their dataSource; this object (C) containing all the stored application data.
C would be, in this case, the M in the MVC.
Add to each of your ViewControllers the following declarations:
// SomeViewController.h
// Before #interface
#class MyDataSource;
// In the interface
IBOutlet MyDataSource *datasource;
#property(retain) IBOutlet MyDataSource *datasource;