Objective-C – Sending data from NSObject subclass to an UIViewController - iphone

I have an UIViewController which has a UITableView inside. In this view controller I want to display some data that I have downloaded from the internet. So for this I have created a helper class called OfficesParser which is supposed to do the following:
Download the data from the internet with ASIHTTPRequest
Process the data with a JSON parser
When finished, send the data back to my view controller
In my view controller I'm allocing and initing my helper class in -viewDidLoad like so:
self.officesParser = [[[OfficesParser alloc] init] autorelease]; //officesParser is a retained property
Then in -viewWillAppear: I call the the method for the officesParser object that will start the download process like so:
[self.officesParser download];
In my helper class OfficesParser ASIHTTPRequest has a delegate method that tells you when a queue has finished downloading. So from this method I want send the data to my view controller. I would think this would work, but it didn't:
- (void)queueFinished:(ASINetworkQueue *)queue {
NSArray *offices = [self offices];
OfficesViewController *ovc = [[OfficesViewController alloc] init];
[ovc setOffices:offices];
}
With this code in mind, how would you achieve what I'm trying to do with proper code?

You need to take a look at delegates and protocols. They're exactly what you're looking for, as they let classes communicate without having to persist a reference. Here is another explanation on them.

Your code:
OfficesViewController *ovc = [[OfficesViewController alloc] init];
Creates new instance property of OfficesViewController. Since it's a new instance it does not have a connection to the OfficesViewController you triggered after downloading and parsing process. To be able to comunicate b/w OfficesViewController and OfficesParser create a modified init method for OfficesParser that allows week pointer to the OfficesViewController.
#interface OfficesParser ()
#property(nonatomic,assign)OfficesViewController *ovc;
#end
#implementation OfficesParser
#synthesize ovc;
-(id)initWithDelegate:(OfficesViewController*)delegate{
ovc = delegate;
return [self init];
}
Now you can access your ovc delegate.
- (void)queueFinished:(ASINetworkQueue *)queue {
NSArray *offices = [self offices];
[ovc setOffices:offices];
}
Finally create your OfficesParser like that
self.officesParser = [[OfficesParser alloc] initWithDelegate: self];

Related

iPhone - When to release the application delegate?

I have an object that is used between all my viewControllers so I have stuck it inside the application delegate. (I assume this is the correct place?).
If an action inside a viewController fires that needs to send something to said object I am performing the following:
- (IBAction)sliderMoved:(id) sender{
MyAppDelegate *delegate = [[UIApplication sharedApplication] delegate];
[[delegate myObject] setSpeed:(int)slider];
// [delegate release];
}
I am a bit concerned I am not releasing the delegate object anywhere, is this ok? If I remove the commented line [delegate release] it just crashes the application.
You don't own the delegate object that you create in this snippet. You haven't created it with alloc, new, or a copy. Nor have you retained it. So, it is not your responsibility to release it.
As for putting an object in the Application delegate just to be able to access it from other parts of your code - that is poor OOP design IMO.
Edited to add
I suppose I had better give an example
Suppose you have a class MyClass that you want to create an object of that you can pass around.
You create it in the Application delegate, which it seems you are already doing:
MyObject *myObject = [[MyObject alloc] init];
Then you create another view controller - which you would normally do, except that this view controller has a property:
#property(retain) MyObject *object;
And then you set this property when you create the view controller:
YourViewController *vc = [[YourViewController alloc] init];
vc.object = myObject;
And, if you pass this object to other view controllers as you require.

dismissModalViewController AND pass data back

I have two view controllers, firstViewController and secondViewController. I am using this code to switch to my secondViewController (I am also passing a string to it):
secondViewController *second = [[secondViewController alloc] initWithNibName:nil bundle:nil];
second.myString = #"This text is passed from firstViewController!";
second.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
[self presentModalViewController:second animated:YES];
[second release];
I then use this code in secondViewController to switch back to the firstViewController:
[self dismissModalViewControllerAnimated:YES];
All of this works fine. My question is, how would I pass data to the firstViewController? I would like to pass a different string into the firstViewController from the secondViewController.
You need to use delegate protocols... Here's how to do it:
Declare a protocol in your secondViewController's header file. It should look like this:
#import <UIKit/UIKit.h>
#protocol SecondDelegate <NSObject>
-(void)secondViewControllerDismissed:(NSString *)stringForFirst
#end
#interface SecondViewController : UIViewController
{
id myDelegate;
}
#property (nonatomic, assign) id<SecondDelegate> myDelegate;
Don't forget to synthesize the myDelegate in your implementation (SecondViewController.m) file:
#synthesize myDelegate;
In your FirstViewController's header file subscribe to the SecondDelegate protocol by doing this:
#import "SecondViewController.h"
#interface FirstViewController:UIViewController <SecondDelegate>
Now when you instantiate SecondViewController in FirstViewController you should do the following:
// If you're using a view controller built with Interface Builder.
SecondViewController *second = [[SecondViewController alloc] initWithNibName:"SecondViewController" bundle:[NSBundle mainBundle]];
// If you're using a view controller built programmatically.
SecondViewController *second = [SecondViewController new]; // Convenience initializer that uses alloc] init]
second.myString = #"This text is passed from firstViewController!";
second.myDelegate = self;
second.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
[self presentModalViewController:second animated:YES];
[second release];
Lastly, in the implementation file for your first view controller (FirstViewController.m) implement the SecondDelegate's method for secondViewControllerDismissed:
- (void)secondViewControllerDismissed:(NSString *)stringForFirst
{
NSString *thisIsTheDesiredString = stringForFirst; //And there you have it.....
}
Now when you're about to dismiss the second view controller you want to invoke the method implemented in the first view controller. This part is simple. All you do is, in your second view controller, add some code before the dismiss code:
if([self.myDelegate respondsToSelector:#selector(secondViewControllerDismissed:)])
{
[self.myDelegate secondViewControllerDismissed:#"THIS IS THE STRING TO SEND!!!"];
}
[self dismissModalViewControllerAnimated:YES];
Delegate protocols are EXTREMELY, EXTREMELY, EXTREMELY useful. It would do you good to familiarize yourself with them :)
NSNotifications are another way to do this, but as a best practice, I prefer using it when I want to communicate across multiple viewControllers or objects. Here's an answer I posted earlier if you're curious about using NSNotifications: Firing events accross multiple viewcontrollers from a thread in the appdelegate
EDIT:
If you want to pass multiple arguments, the code before dismiss looks like this:
if([self.myDelegate respondsToSelector:#selector(secondViewControllerDismissed:argument2:argument3:)])
{
[self.myDelegate secondViewControllerDismissed:#"THIS IS THE STRING TO SEND!!!" argument2:someObject argument3:anotherObject];
}
[self dismissModalViewControllerAnimated:YES];
This means that your SecondDelegate method implementation inside your firstViewController will now look like:
- (void) secondViewControllerDismissed:(NSString*)stringForFirst argument2:(NSObject*)inObject1 argument3:(NSObject*)inObject2
{
NSString thisIsTheDesiredString = stringForFirst;
NSObject desiredObject1 = inObject1;
//....and so on
}
I could be way out of place here, but I am starting to much prefer the block syntax to the very verbose delegate/protocol approach. If you make vc2 from vc1, have a property on vc2 that you can set from vc1 that is a block!
#property (nonatomic, copy) void (^somethingHappenedInVC2)(NSString *response);
Then, when something happens in vc2 that you want to tell vc1 about, just execute the block that you defined in vc1!
self.somethingHappenedInVC2(#"Hello!");
This allows you to send data from vc2 back to vc1. Just like magic. IMO, this is a lot easier/cleaner than protocols. Blocks are awesome and need to be embraced as much as possible.
EDIT - Improved example
Let's say we have a mainVC that we want to present a modalVC on top of temporarily to get some input from a user. In order to present that modalVC from mainVC, we need to alloc/init it inside of mainVC. Pretty basic stuff. Well when we make this modalVC object, we can also set a block property on it that allows us to easily communicate between both vc objects. So let's take the example from above and put the follwing property in the .h file of modalVC:
#property (nonatomic, copy) void (^somethingHappenedInModalVC)(NSString *response);
Then, in our mainVC, after we have alloc/init'd a new modalVC object, you set the block property of modalVC like this:
ModalVC *modalVC = [[ModalVC alloc] init];
modalVC.somethingHappenedInModalVC = ^(NSString *response) {
NSLog(#"Something was selected in the modalVC, and this is what it was:%#", response);
}
So we are just setting the block property, and defining what happens when that block is executed.
Finally, in our modalVC, we could have a tableViewController that is backed by a dataSource array of strings. Once a row selection is made, we could do something like this:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *selectedString = self.dataSource[indexPath.row];
self.somethingHappenedInModalVC(selectedString);
}
And of course, each time we select a row in modalVC, we are going to get a console output from our NSLog line back in mainVC. Hope that helps!
hmm, look for the notification centre and pass back info in a notification. here is apples take on it
- I take this approach personally unless any one has any other suggestions
Define a delegate protocol in the second view controller and make the first one the delegate of the second.

ViewDidLoad Method doesn't called in iPhone?

I have passed the NSMutableDictionary into the FirstClass using custom init method.
Second Class,
firstClass = [[FirstClass alloc] initWithFeedData:feedDictionary];
[self.navigationController pushViewController:firstClasss animated:YES];
FirstClass,
- (FirstClass *) initWithFeedData:(NSMutableDictionary *) dataDictionary {
{
NSLog(#"%#", dataDictionary);
[[NSBundle mainBundle] loadNibNamed:#"FirstClass" owner:self options:nil];
//[self viewDidLoad]; Without call, ViewDidLoad method doesn't call.
}
- (void) viewDidLoad {
[super viewDidLoad];
NSLOG(#"Inside View DidLoad"); // This method never calls.
}
In my viewDidLoad method doesn't called, if i call "[self viewDidLoad]" then only, viewDidLoad method works properly. I donno why viewDidLoad method doesn't call directly without calls in another method? Please Help me out.
Thanks.
If you're subclassing a UIViewController, which I assume you are since you're expecting viewDidLoad, then you should override its designated initialiser, initWithNibName:bundle: if you're using a XIB, in which case viewDidLoad will be called after the XIB loads. If you're not using a XIB, you should implement the loadView method to create your view, and viewDidLoad will be called after loadView finishes.
#Stephen is right, at the very least you need to rewrite your init statement to return self. However, it's much easier and more robust to declare a property on your view controller and pass objects in that manner.
FirstClass *firstClass = [[FirstClass alloc] initWithNibNamed:nil];
firstClass.feedDictionary = feedDictionary;
[self.navigationController pushViewController:firstClasss animated:YES];
[firstClass release];
Your viewDidLoad method will now be called and your feedDictionary will be sitting there, waiting for you.
Your init method is not correct. It should look something like this:
- (id) initWithFeedData:(NSMutableDictionary *) dataDictionary {
if (self = [super init]) {
// TODO: do initialisation stuff with dataDictionary
}
return self;
}
This assumes that FirstClass is derived from UIViewController.
As long as your NIB file has the same name as your class it should be loaded in automatically -- there's no need for the loadBundle: call. Here's what the documentation says about the nibName property:
If the value of this property is nil
and you did not override the loadView
method in your custom subclass, the
view controller looks for a nib file
whose name (without the .nib
extension) matches the name of your
view controller class.

Messaging between ViewControllers Using Delegates

I'm trying to send an array from one viewController to another using protocols and delegates. I declared the delegate on viewcontroller B and used the following code in viewcontroller A to send the message from A to B. The protocol's method is didReceiveMessage. Unfortunately, the message never arrives.
Attached is the code from viewController A
- (IBAction) graphPressed:(UIButton *)sender {
GraphingViewController *gvc=[[GraphingViewController alloc] initWithNibName:nil bundle:nil];
[self presentModalViewController:gvc animated:YES];
[gvc release];
[delegate didReceiveMessage:brain.internalExpression];
}
and the code from viewcontrollerB
- (IBAction) ViewdidLoad {
CalculatorViewController *cvc =[[CalculatorViewController alloc] init];
cvc.delegate=self;
[cvc release];
}
- (void) didReceiveMessage:(NSMutableArray *)expression {
NSLog(#"message received from CalculatorAppDelegate");
}
Any suggestions would be greatly appreciated.
I'm not sure what are you doing in second sample? You created an object, assign its delegate property and then released it. Why?
If that is code from your application it could probably be from releasing cvc at the end of your ViewDidLoad method.
init would give it a retain count of 1, and the release would take it back to 0.
Also the code seems sort of backwards, if you wanted to set view A as the delegate for view B, you would do so in view A.
Unless there is a more complex reason to use a delegate that I'm not seeing from the code, I would just keep a pointer around to the child if you really need to send it messages.
Like others have posted, you are just getting rid of the Calculator VC after creating it. I would recommend adding an #property to the second VC and using it to store a pointer to the Calculator. It should be a retain property. Then set the delegate of that property to self.
Also, you make sure to use an assign property for the delegate value, and try to use the property syntax (self.delegate) whenever possible.
There could absolutely be more going on here, so if this doesn't actually solve the problem try and post up more of your code (header files, what connections are made in IB, etc.)
Edit: For the record, the method is -(void)viewDidLoad, not -(void)ViewDidLoad, so that could be contributing to the problem.
As you said you are trying to pass an array from one view controller to another.Well i use in this way.Here is the code
- (IBAction) graphPressed:(UIButton *)sender {
GraphingViewController *gvc=[[GraphingViewController alloc] initWithNibName:nil bundle:nil];
gvc.myArray=infoArray;
[self presentModalViewController:gvc animated:YES];
[gvc release];
}
Where myArray is array in your GraphingViewController,You just need to make property of this array with retain attribute,simply as
#property(nonatomic,retain)NSMutableArray *myArray;
And you need to Synthesize it as well.

removeFromSuperview is not removing view calling from another class

i am getting the web services from the .net web server.
while in the process (getting data) i am displaying a subview with activity indicator.
After completing getting data i need to close that view.
i have two classes one is myclassviewcontroller,webservices
Basically i am writing code to get web services webservices.
In webservices class at
-(void)connectionDidFinishLoading:(NSURLConnection *)connection i call myclass like this.
myclassviewcontroller *obj = [[myclassviewcontroller alloc]init];
[obj mymethod];
At myclassviewcontroller i write this code for my method.
(void)mymethod {
[loadview removeFromSuperview];
}
the method is executed but view is not removed.
I already declared it in myclassviewcontroller.h class also.
i am checking this by keeping some text in NSlog
But if i calling this mymethod in myclassviewcontroller.m using timer then it removes view.
what the wrong.
can any one please help me.
I think it may be understand what is my problem.Let me place comment if not.
Thank u in advance.
I believe the problem with your code is how you access the myclassviewcontroller. It must have already been on the screen while the data was loading, so creating a new instance of that class and calling a method against one of it's uninitialized members (loadview) does nothing.
myclassviewcontroller *obj = [[myclassviewcontroller alloc]init];
// here object has just been initialized
// (it is not the same instance as the one on screen)
[obj mymethod];
If obj was a reference to the actual viewcontroller that is on screen, you could easily call:
[obj.loadview removeFromSuperview];
or
[obj mymethod]; // if you wanted to add more code in that function
So, the real problem is that you accessing a different instance of myclassviewcontroller than the one which is actually on screen. You need a variable holding some reference to the correct instance of myclassviewcontroller to access the loadview ivar.
In webservices.h:
#interface webservices : NSObject {
...
// This ivar will have to be set when webservices is initialized
myclassviewcontroller * viewController;
}
#property (nonatomic, retain) myclassviewcontroller * viewController;
and webservices.m would need to #synchronize viewController.
Then in connectionDidFinishLoading: you can just call [viewController.loadview removeFromSuperview];
the problem could be that you instantiate your myclassviewcontroller when "loadview" is already allocated by your "main" class but "invisible" in your myclassviewcontroller, so your new instance of myclassviewcontroller doesn't really know who "loadview" is...
i mean: loadview is allocated and added to the mainView (in the same class where you allocate
"myclassviewcontroller"...)
but then you try to remove it not in your mainView, but in myclassviewcontroller...
try to modify your method this way:
(void)mymethod {
if (loadview!=nil){
NSLog(#"I'm here...");
[loadview removeFromSuperview];
}
}
to see if "loadview" exist when and WHERE you call the method (in myclassviewcontroller)
luca