How to update a variable in one class from another? - iphone

I need to set a variable in Class A from Class B. To test this, I have a while loop running in Class A that continuously prints the variable via NSLog. However, no matter what I try, I cannot get Class B to update the variable in Class A in such a way that Class A can read the changes made by Class B. I am pretty sure I have everything hooked up properly in IB. Here's how I have things set up:
//Class A
#interface AppDelegate : NSObject {
NSString *teststring;
}
#property(readwrite,nonatomic,retain) NSString *teststring;
#end
#implementation AppDelegate
#synthesize teststring;
-(id)init{
self = [super init];
if(self) {
teststring = [[NSString alloc] init];
}
return self;
}
-(void)awakeFromNib
{
while(1){
NSLog(#"teststring is %#",teststring);
usleep(500000);
}
}
#end
//Class B
#class AppDelegate;
#interface otherClass : NSObject {
AppDelegate *appdel;
}
-(IBAction)doTest:(id)sender;
#end
#implementation otherClass
-(void)awakeFromNib
{
appdel = [[AppDelegate alloc] init];
}
-(void)doTest:(id)sender
{
appdel.teststring = #"Test";
NSLog(#"Set teststring to %#",appdel.teststring); //this works
}
#end

You are thinking too much about classes (as seemingly some sort of “department” of code) and not enough about objects, and both your wording and your problem demonstrate this.
You have your instance of otherClass creating a second instance of the AppDelegate class. You already had one AppDelegate instance, which is the actual application delegate (because, I assume, you have it in your nib and you have it hooked up to the application's delegate outlet there); now, in -[otherClass awakeFromNib], you are creating another.
You then tell this second AppDelegate instance to set its teststring property to #"Test", and then you ask your second AppDelegate instance for the value of that property, and your second AppDelegate instance dutifully shows you the value you gave it.
The first instance doesn't have the same value for its teststring property because the otherClass object never gave that instance a value for its teststring property. Note that the variables you define in the #interface section are instance variables, which is why different instances of a class can and usually will have different values in those variables. Properties are likewise per-instance, being usually backed by these instance variables.
AppDelegate A (the real application delegate, created in the nib) and AppDelegate B (created by the otherClass object, not anything's delegate) are two separate instances of AppDelegate, with separate teststring variables.
Thus, the solution: Have the otherClass instance talk to the real application delegate, not an AppDelegate instance that it created itself. You could ask the application for its delegate, or (if the otherClass object is in the MainMenu nib) give it an outlet to the application delegate, just like the application has.
However, piling too much stuff into your application delegate class is bad design; each class should have one specific purpose, and generally should fit neatly within one of the Model, View, and Controller classifications. So, assuming your otherClass object should be a controller, move the property into otherClass and make that object the controller of whatever needs the property.

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.

Can't access class instance from another class

in my AppDelegate I have imported the header of a class I have created and propertied and syntesized an instance of it (in AppDelegate). Now I'm trying to access a method and a variable inside this instance from two other views. I'm relatively new to objective-c, but so far I've learned that if I do this:
AppDelegate *appdelegate = [AppDelegate new];
I will just get a fresh instance from the class inside AppDelegate, so if I set a variable from one view, I can't access it from the other. I've read that if I would do it this way:
AppDelegate *ap = (AppDelegate *)[[UIApplication sharedApplication] delegate];
It would allow me to access the existing instance. But it doesn't work.
Is the way I'm trying to do this totally wrong? Thanks a lot for your help!
UPDATE:
When I do this inside AppDelegate:
myClass.string = #"test";
NSLog(#"appDelegate: %#", myClass.string);
I get this:
appDelegate: (null)
UPDATE2:
I wrote #class AppDelegate; underneath the #import lines in the viewController, but still I can't access myClass. A main problem, which may be the cause why this isn't working from the views, is that I can't even access myClass from AppDelegate.
In AppDelegate.h I wrote:
#property (strong, nonatomic) testClass *myClass;
In AppDelegate.m:
#import "testClass.h"
#synthesize myClass;
This should be right, right?
myClass.h
#property (strong, nonatomic) NSString *string;
myClass.m
#synthesize string;
When I then try to access myClass from appDelegate, I write:
self.myClass.string = #"test";
NSLog(#"appDelegate: %#", self.myClass.string);
The result is:
appDelegate: (null)
I think you have to allocate and initialize the myClass
Write myClass = [MyClass alloc] init] in AppDelegate.m file
#import "AppDelegate"
and also write
#class AppDelegate;
Unless you haven't shown it, you're not allocating testClass and not assigning it to myClass. Objective-C is not like C++ or Java where you can simply declare a variable of a particular class type and have it instantiated on the stack. Each class you use must be instantiated, whether manually or through InterfaceBuilder. The exception is there are some classes provided by the various frameworks which have a single shared instance. Rather than allocating those classes, you simply ask for the shared instance. However, that's not the case here. It's your own class, so you need to allocate it.
It would look like:
myClass = [[testClass alloc] init];

One delegate class for multiple NSFetchedResultsController?

I have several NSFetchedResultsControllers throughout my app, and in every view controller, I implement the respective delegate methods. However, instead of copying these delegate methods into every class that implements an NSFetchedResultsController, I thought I would just create a class that implements these delegate methods, and set all fetched results controller's delegate to point to that one class. Here's what I've tried, which doesn't work:
Since the delegate methods need to know which table view they are making changes to, I thought I would just create a separate delegate class for each fetched results controller, and send a pointer to the tableview for that class:
FetchedResultsDelegate *delegate = [[FetchedResultsDelegate alloc] initWithTableView:parentTableView];
self.fetchedResultsController.delegate=delegate;
[delegate release];
However, this causes a BAD_ACCESS crash, so this means that I probably shouldn't be doing what I'm doing above.
How can I create a single delegate class that handles all delegate requests for all my NSFetchedResultsControllers?
Edit: I was able to fix the problem by doing #property (nonatomic, retain) FetchedResultsDelegate *delegate; Is this ok? Some people are saying something about assign rather than retain?
Nothing is retaining your FetchedResultsDelegate as delegate properties are normally declared as assign. e.g. NSFetchedResultsController declares the delegate as
#property(nonatomic, assign) id <NSFetchedResultsControllerDelegate> delegate
Therefore you created the object and destroyed it straight away, but gave the fetchedResultsController a nasty dangling pointer.
To fix this you need a retain on the delegate. So in your UITableViewController class add a new property
// .h
#property (nonatomic, strong) id<NSFetchedResultsControllerDelegate> tableViewDelegate;
// .m
#synthesize tableViewDelegate = _tableViewDelegate;
then when you hook up you delegate just change your code to this
FetchedResultsDelegate *delegate = [[FetchedResultsDelegate alloc] initWithTableView:parentTableView];
self.fetchedResultsController.delegate = delegate;
self.tableViewDelegate = delegate;
[delegate release]; delegate = nil;
Don't forget
Release this new ivar in the dealloc
- (void)dealloc;
{
// ... other releases
[_tableViewDelegate release];
[super dealloc];
}
The use of assign is all about ownership semantics.
In this case your UITableViewController should own the tableView's delegate (e.g. strong/retain) as nothing else is.
The reason that the NSFetchedResultsController uses assign and not retain/strong is because there is a good chance that the object that created it would act as the delegate, which would result in both objects owning each other (both having a retain held on each other), which causes a retain cycle

How to work with inheritance in objective-C (iOS sdk)

I just started to learn iOS programming and I have a problem with inheritance. There are 2 files.
First file
Header
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController {
int x;
}
#end
Implementation:
#import "ViewController.h"
#import "NewClass.h"
#implementation ViewController
#pragma mark - View lifecycle
- (void)viewDidLoad
{
[super viewDidLoad];
x = 999;
NewClass *myClass = [[[NewClass alloc] init] autorelease];
}
#end
Second file
Header:
#import "ViewController.h"
#interface NewClass : ViewController
#end
Implementation:
#import "NewClass.h"
#implementation NewClass
-(id)init {
self = [super init];
if (self != nil) {
NSLog(#"%i",x);
}
return self;
}
#end
In ViewController I set x to 999, and in NewClass I want to get it, but when I call NSLog(#"%i",x); it gives me 0.
Where did I make a mistake?
You have a timing problem.
The init method gets called first (at all levels of the inheritance hierarchy, so in both ViewController and NewClass). This is when you print out your value of x, when it is still zero.
The viewDidLoad method only gets called much later, generally at a point after a view controller's view has been added to a superview. It's functionality that's specific to the UIViewController class.
To make your example work, put an init method in your ViewController class that looks like the one in your NewClass class, and set x there.
Also, you don't need to create a NewClass instance within ViewController. When you create a NewClass object, it is automatically a ViewController as well. In the same way that a dog is an animal automatically, and so is a cat. When you create a dog, you don't want to create an animal as well!
As sidyll says, you should probably do a bit more reading about how inheritance works, I'm afraid!
You need to review you OOP concepts. Object-Oriented Programming with Objective-C is a must.
Your class NewClass indeed inherits the x variable, but not it's value. When you create an instance of it, you're creating a shiny new instance whose values have nothing to do with the parent class.
Another point of view to help you is that x was set in a object of ViewController class. The NewClass inherits from ViewController class, not from an arbitrary instance (object, where you set x).
That's because -viewDidLoad is not called until well after -init returns. Your superclass should do configuration like that in its -init method.

How do I work between classes in Objective-C?

At the moment, the majority of my code is in the same viewcontroller, and i'd like to move some of it over to other areas. Such as moving the animations all over to somewhere else. But then how do i reference things which are in another class? And how do i reference back from that class to items in my viewcontroller class? Not going this has always disuaded me from doing it.
there is a couple of ways you can achieve that.
one way is the cocoa delegate #protocol way, the second way could be creating references to each object in the other class.
for the first way you can do something like this:
#class Class2;
#interface Class1 : NSObject {
Class2 *cls2Pointer;
}
#property Class2 *cls2Pointer;
#end
#class Class1;
#interface Class2 : NSObject {
Class1 *cls1Pointer;
}
#property Class1 *cls1Pointer;
#end
int main(){
Class1 cls1Obj = [[Class1 alloc] init];
Class2 cls2Obj = [[Class2 alloc] init];
[cls1Obj setCls2Pointer:cls2Obj];
[cls2Obj setCls1Pointer:cls1Obj];
}
the second way, is to declare a protocol in one/both of the classes to be able to pass arguments and call different methods on other objects:
#protocol Class1Delegate
- (void)class1:(Class1)obj MethodWithArg:(id)arg;
#end
#interface Class1 : NSObject {
id <Class1Delegate> delegate;
}
#end
#interface Class2 : NSObject <Class1Delegate>{
}
#end
#implementation Class2
- (void)class1:(Class1)obj MethodWithArg:(id)arg {
//do stuff when called from the 1st class
}
#end
You might like to look into this here - to create static classes in objective c and then reference them in a separate file by classname - as in the view controller quoted in the linked example.
Otherwise you can just create a new class within a separate .m file and then code it such that the calling method in another class will first create an instance of this new class and then invoke the necessary method on this instance.
Hope this helps.
Basically what you do is that you create one or more classes, move the code over to these classes and then create instances of these classes in your viewcontroller.
so if you had a method in your view controller
-(void)foo;
you would create a new class say C and move the method there.
then in your view controller you would create an instance variable of that class e.g.
C* myC;
then alloc/init and then call the foo method. This is not object oriented in the sense that foo is not really related to C in any way so method foo could have just been a static method not relating to the instance and as such called just like any other method but as [C foo] instead of [self foo] from the view controller.
the other more OOP method would be to move functionality that belongs to together into a separate class like animation in your example.