Passing array between view controllers? - iphone

I really need some more help!
I am trying to pass an array from one view controller to another. I think the latter being a 'child' view controller?
My code is as follows:
MainViewController.h:
#import <UIKit/UIKit.h>
#import <AudioToolbox/AudioToolbox.h>
#import <AVFoundation/AVFoundation.h>
#interface HelloWorldIOS4ViewController : UIViewController <UITableViewDelegate, UITableViewDataSource, UITextFieldDelegate, AVAudioPlayerDelegate> {
NSMutableArray *countProductCode;
UIPopoverController *detailViewPopover;
}
#property (nonatomic, retain) NSMutableArray *countProductCode;
#property (nonatomic, retain) UIPopoverController *detailViewPopover;
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data;
...
#end
MainViewController.m
#import "HelloWorldIOS4ViewController.h"
#import "JSON.h"
#import "PopoverContentViewController.h"
#implementation HelloWorldIOS4ViewController
#synthesize detailViewPopover;
#synthesize countProductCode;
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
NSString *jsonString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSDictionary *results = [jsonString JSONValue];
NSLog(#"RETURN: %#", results);
[countProductCode removeAllObjects];
NSArray *products = [results objectForKey:#"items"];
for (NSDictionary *row in products)
{
NSString *code = [row objectForKey:#"ic"];
[countProductCode addObject:code];
}
PopoverContentViewController.countProductCodes = countProductCode;
}
PopoverViewController.h:
#interface PopoverContentViewController : UITableViewController {
NSMutableArray *countProductCodes;
}
#property (nonatomic, retain) NSMutableArray *countProductCodes;
#end
PopoverViewController.m:
#import "PopoverContentViewController.h"
#import "HelloWorldIOS4ViewController.h"
#implementation PopoverContentViewController
#synthesize countProductCodes;
...
I have cut a lot out, but I know from a load of NSLog's dotted around that I get the data back etc, but I cannot pass the array countProductCode to the PopoverViewController's countProductCodes array.
I keep getting
"Accessing unknown 'setCountProductCodes:' class method"
errors.
This may be something really silly that I'm doing but it's driving me crazy!
Can anyone help please?
Thanks
James

Dear James,
I think you would like to have a closer look at the Model-View-Controller paradigm. In your app you are trying to implement some kind of a "super class". Let me explain what that means:
In your MainViewController class which is clearly a controller there is also some part of the model implemented. This is a bad idea, but a very common one to make in the beginning. Maybe I misunderstood your design, but here is how I would implement it:
The Model I would implement a proper model object, which could be in your case as easy as a custom NSObject subclass with a NSMutableArray as a property. In addition this model would have the methods for retrieving data off the internet implemented. That is right: do the networking in the model. You would have to have methods like - (void) refreshProductCode that you would call from your controller. If you want to get really fancy, use an NSOperation to encapsulate the download (then you would use the a synchronous variant of NSURLConnection, because the operation itself is already executed asynchronously) The nice thing would be then if your parsing of the JSON string takes longer, also this is performed in the background and your UI will stay responsive.
So now the model is downloading your stuff - great, but how do I know when it is done? Well you would post a Notification from the model once it is done. What if the download fails? You guessed it right: post a notification that it failed.
The Controller The controller which needs to display data from the model would first to get the model object. In this case the model object is a property on your AppController. The controller then has a property for this model object and retains it, so that the model object does not go away while the controller lives. The controller then also registers for notifications of the model. So how would a typical download work then?
Get an instance of the model object
call -(void) refreshProductCode on the model object
display network activity spinner in status bar and wait for notifications
when the notification came in, on success refresh the UI and on failure restart download or display a note to the user. Also disable the network activity spinner.
How do you shuffle data between view controllers? View controllers should operate a bit like the mafia: every view controller is working on a need-to-know basis. For example if you want a view controller to display the details of your product, you would not pass the model with all your products to the controller. Instead you would have an instance variable on the detail view controller holding only one produce model object, which has all the information like description text, photo etc. The cool thing then is if you ever want to display product information again in you app, you can reuse that view controller, as all it needs is a product model object to do its job.

In your code:
PopoverContentViewController.countProductCodes = countProductCode;
should be:
popoverContentViewController.countProductCodes = countProductCode;
Your instance name should be different from the class name.

In the mainViewController class, in the following method
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
you are accessing the "countProductCodes" using class name. You should access using its object.
like
PopoverContentViewController *obj = [[PopoverContentViewController alloc] init];
obj.countProductCodes = countProductCodes;

In MainViewController.h:
+(NSMutableArray)arrayRes;
In MainViewController.m
+(NSMutableArray)arrayRes{
return countProductCode;
}
perform any of the code changes on countProductCode array as usually
In PopoverViewController.m
declare #class MainViewController; and in viewDidLoad
NSMutableArray *newArray;
newArray = [MainViewController arrayRes];

Related

Passing data between ObjectClass and ViewController

I am trying to pass some data back to my view controller from an object class.
This is a basic over view of what classes and views are doing, and then I will show you my code.
So ViewController, loads its tableviewcells. Then inside this delegate method it calles a connection class I have created, inside that conection class is NSURLConnection methods connecting and downloading data from the database, in the connectionDidFinishLoading method of this connection class I set up a parsing class and pass all of the downloaded data over to that.
Then I parse that data, and at the end of that parser inside parserDidEndDocument, I am trying to send the data that is now in an array variable back to my view controller to display. However.. for some reason my protocols and delegates are not working.
I have set up protocols inside m parser class and set the delegates in my view controller but it never makes it to my protocol method.
I will show you my code below.
parserclass.h
#protocol PassParsedData <NSObject>
#required
- (void)sendManufactureArray:(NSArray *)array;
#end
//..
id <PassParsedData> delegate;
//..
#property (strong) id delegate;
//
parserclass.m
#import "VehicleSearchViewController.h"
//..
#synthesize delegate;
//..
- (void)parserDidEndDocument:(NSXMLParser *)parser
{
//.. array stuff is set up and i can log it so i know its working.. i just want to show you the protocol and delegate stuff to be clear
[[self delegate]sendManufactureArray:filteredArray];
}
Then moving onto my view controller where I am hoping to call the protocol and get the filteredArray data back.
ViewController.h
#import "EngineResponses.h" //delegates & protocols
//..
#interface SearchViewController : UITableViewController <PassParsedData> {
//..
Viewcontroller.h
#import "EngineResponses.h"
//..
- (void)viewDidLoad
{
[super viewDidLoad];
//..
parserClass *pc = [[parserClass alloc] init];
[pc setDelegate:self];
//..
}
- (void)sendManufactureArray:(NSArray *)array //Breakpoint here is never accessed
{
FilterArray = array;
[self.tableView reloadData];
}
As you can see in that last method which is the protocol I am calling, its never accessed by the thread. I have checked, This call
[[self delegate]sendManufactureArray:filteredArray];
gets accessed fine.. but it just never makes it back to the View Controller.. any ideas... am I missing anything? .. im at a complete loss, been working on this all day.
any help would be HUGELY appreciated! :)
UPDATE:
I have added this to my ViewController.h
//..
ParserClass *parserclass;
//..
#property (strong, nonatomic) ParserClass *parserclass;
//..
viewcontroller.m
#synthesize parserclass;
//..
//then I call this in viewdidload
[engineResponses setDelegate:self];
As others have said in the comments, your pc variable is being deallocated at the end of the viewDidLoad block, since the only reference it has goes out of scope there. If you want it to live on, you must enlarge its scope (i.e. make it an instance variable).

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

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.

How to share state between viewControllers using the AppDelegate object

I'm trying to share an NSArray object between several different view controllers and my first thought was to add a property on the app delegate as they all have access to this object.
But after some debugging it appears I can't actually share this array for some reason. When I set the object from the first view controller and NSLog the results all is well. But when I attempt to get that array value using another view controller object it always returns UITouchData (not the value previously shown in the logs after my first view controller set the value)
Here is the code that I'm using to set the value
NSArray* cookies = [NSHTTPCookie
cookiesWithResponseHeaderFields:[response allHeaderFields]
forURL:[NSURL URLWithString:#""]];
[appDelegate setAuthCookie:cookies];
Here is part of the .h for my app delegate
#interface SomeAppDelegate : NSObject <UIApplicationDelegate> {
NSArray* authCookie;
}
#property (retain) NSArray* authCookie;
- (void)setAuthCookie:(NSArray *)cookie;
- (NSArray *)getAuthCookie;
#end
Here is the .m for the methods in question
#synthesize authCookie;
- (void)setAuthCookie:(NSArray *)cookie
{
authCookie = cookie;
}
- (NSArray *)getAuthCookie
{
return authCookie;
}
Here is the attempt to grab this array in the second view controller that fails (technically it doesn't fail on this line but I don't get an NSArray back as expected so when I try to use this it fails)
NSArray* cookies = [appDelegate getAuthCookie];
Any way I can share state using the app delegate like this?
Your memory management is wrong, and you are getting a completely different object which has inherited the old array's address when you later use the getter.
Your #property is correct, but you've written your own setter and getter that do not retain the object. You don't need to use both #property/#synthesize and supply your own getter/setter. The former is a newer means of automating the latter.
If you remove your implementations of setAuthCookier: and getAuthCookie then your code should work.

AppDelegate: Get value from ViewController?

I would like to get at
- (void)applicationWillTerminate:(UIApplication *)application
a variable from a view controller class.
I have build an tabbar application and only added the tabbar controller to the appdelegate.
[window addSubview:tabBarController.view];
How can i get an variable from the TestViewController:
#import <UIKit/UIKit.h>
#import <AVFoundation/AVFoundation.h>
#interface TestViewController : UIViewController {
IBOutlet UILabel *testLabel;
NSString *currentString; //Value that i want to save at applicationWillTerminate
}
#property (nonatomic, retain) UILabel* testLabel;
#property (nonatomic, retain) NSString* currentString;
#end
It's somewhat incidental that TestViewController hasn't been dealloc'd by the time you reach applicationWillTerminate - it might make sense to store that value a level higher in your application. That approach would be to always store currentString in the UIApplicationDelegate so that you don't have to fetch it later:
#implementation TestViewController
- (void)setCurrentString:(NSString *)currentString {
((MyAppDelegate *)[[UIApplication sharedApplication] delegate]).currentString = currentString;
}
#end
Expanding on dbarker's answer, it sounds like what you really need is to save the currentString value in your data model. The proper place to do that is in the viewController itself.
If your data model is just that one string, you can create a property in the app delegate to hold it. Then the viewController writes to the app delegate property whenever the value of currentString changes in a view and/or its value when the view closes.
This way, the data (which is the entire point of the app anyway) is always in place when the app closes regardless of how many views you open.
It is the proper role of controllers to move information from the interface to the data model. Strictly speaking, the viewController shouldn't store any data at all beyond that needed by the interface itself. That should be a property of the data model which the viewControllers set by sending a message to the data model object with the values taken from the interface.
In this case, you would not have a currentString property in your view controllers. Instead they would have a property that is just a reference to the data model's currentString property. The view controllers would continuously update that property but would store nothing themselves.
The advantage of this design is obvious. If you need the value anywhere in your app, you have one location and one call to get it. No single part of the app needs to even know of the existence of any other part of the app save for the data model.
Not 100% sure what you are asking for but here is a guess:
UITabBarController's have a property called viewControllers which return all view controllers associated with the tabbar.
Assuming that the TestViewController was the first tab you could get to it by:
- (void)applicationWillTerminate:(UIApplication *)application {
TestViewController* test_view_controller = [tabBarController.viewControllers objectAtIndex:0] ;
NSString* value = test_view_controller.currentString ;
}
Note this would break if you decided to later move the TestViewController to a different position in the tabbar.
-- Edit --
Check all controllers and get the string from the controller that is of type TestViewController.
NSString* value = nil ;
for ( id unknownController in tabBarController.viewControllers ) {
if ( [unknownController isKindOfClass:[TestViewController class]] ) {
value = ((TestViewController*)unknownController).currentString ;
}
}
// value should be the value of the string.