This question already has answers here:
Passing data between view controllers
(45 answers)
Closed 8 years ago.
I am having trouble passing my NSMutableArray data from one view to the other.
My view transition works the following way
Send View -> Recipients View
Recipients View -> Send View
In the recipients view I am grabbing the people I want to send my data to. Once I grab the people using an array, I go back to my send view to send those people that data.
In my send view I have the following NSMutableArray
#property (nonatomic, strong) NSMutableArray *sendTo;
I initialize it in viewDidLoad like so:
self.sendTo = [[NSMutableArray alloc] init];
In my Recipients View I have the following NSMutableArray
#property (nonatomic, strong) NSMutableArray *recipientsArray;
I initialize it in the viewDidLoad like so:
self.recipientsArray = [[NSMutableArray alloc] init];
I also have a back button in this controller which tries to handle the passing of the data in the recipientsArray. Please note this array does have data in it up until the point of the view switch.
- (IBAction)back {
SendView *send = [self.storyboard instantiateViewControllerWithIdentifier:#"View"];
send.sendTo = [NSMutableArray arrayWithArray:self.recipients];
[self dismissViewControllerAnimated:YES completion:nil];
}
Any idea why the array in my Send View is returning nil?
This code creates a brand new instance of SendView...
SendView *send = [self.storyboard instantiateViewControllerWithIdentifier:#"View"];
This code sets the sendTo property on the brand new instance of SendView.
send.sendTo = [NSMutableArray arrayWithArray:self.recipients];
At the end of the back method, this instance of SendView goes out of scope and is gone forever.
In conclusion, you must get a reference to your existing instance of SendView in order to do anything meaningful.
Related
I have an iOS application which needs to pass an NSArray between two ViewControllers. However when the NSArray gets passed its blank.... (in the view that it gets passed to).
Here is my code:
VIEW ONE - header file
// Detail view property - pass data on.
#property (nonatomic, retain) DetailView *data_pass;
VIEW ONE - implementation file
#synthesize data_pass;
NSArray *image_iphone = [[res objectForKey:#"results"] valueForKey:#"pictures"];
UIStoryboard *newStoryboard = [UIStoryboard storyboardWithName:view_name bundle:nil];
DetailView *firstvc = [newStoryboard instantiateInitialViewController];
self.data_pass = firstvc;
data_pass.input_image = image_iphone[indexPath.row];
[self presentViewController:firstvc animated:YES completion:nil];
VIEW TWO - header file
#property (nonatomic, retain) NSArray *input_image;
VIEW TWO - implementation file
#synthesize input_image;
NSLog(#"VIEW DID APPEAR LOG: %#\n\n", input_image);
In the first view controller "VIEW ONE" I have checked the NSArray that I am passing and it is NOT blank at all...
Here is the output I get in the second view controller "VIEW TWO":
VIEW DID APPEAR LOG: (
)
So what am I doing wrong?
So I figured out what was wrong in the end. But thank you to all the people who left comments, they helped a lot!!
The issue was with two things:
1) As #RamaniAshish said - I was passing indexPath.row and NOT the whole NSArray. For some reason this means that the contents of the array doesn't get passed.
2) The second issue was with one of my if statements which was checking for the wrong thing and so the correct array that was being passed in was not being checked in the second view controller.
Thanks for all you help fellow devs :)
I'm can see that there is a lot of questions regarding this already, but none of them seems to have given me an explanation to why I cannot access my array from another class.
Here's where I wan't to access the array
(XYZPaymentViewController.m)
- (void)viewDidLoad
{
[super viewDidLoad];
XYZMateOverviewViewController *test = [[XYZMateOverviewViewController alloc] init];
NSMutableArray *t = test.mates;
NSLog(#"Count of ThatArray: %d", [t count]);
}
Nomatter what - the log writes out 0 !!
In another class (XYZMateOverviewViewController.h) I declare the array
#property (retain) NSMutableArray *mates;
I synthesize the array in the implementation area in XYZMateOverviewViewController.m
#synthesize mates;
I hope you can help me understand what I'm doing wrong :)
If you want to access an array from another ViewController you have to pass the array between the ViewControllers.
For example:
You have two ViewControllers. 'A' and 'B', so u have to do this:
In the 'A' ViewController, is where you have the array that you want to send to another ViewController. Then create an instance of 'B' ViewController and send the array to him:
CODE OF 'A' ViewController:
NSMutableArray *arrayToSend = [[NSMutableArray alloc]init];
BViewController *bViewController = [[BViewController alloc]initWithNibName:#"BViewController" bundle:nil];
BViewController.arrayReceived = arrayToSend;
[self.view addSubView:BViewController.view];
After, in your BViewController you just have to take your arrayReceived and use it.
CODE OF 'B' ViewController:
NSLog(#"%#", [arrayReceived objectAtIndex:0]); // For example.
Hope i did help you, if u have any question tell me.
What are you doing with the mates array in the XYZMateOverviewViewController init method?
If you're not adding any data to it a count of 0 is just right.
With the line
XYZMateOverviewViewController *test = [[XYZMateOverviewViewController alloc] init];
you are creating a completely new instance of XYZMateOverviewViewController—one that has nothing to do with any other part of your application*. What you should be doing instead is getting a reference to the already-set-up instance of the view controller. That instance presumably has its mates property set up with the data you want.
The general topic of sharing data between different parts of your application—and between different view controllers, in particular—should be covered somewhere toward the beginning of any “introduction to iOS programming” book or tutorial.
* Unless you’re doing something funky with singletons or shared state, of course, but I doubt that’s the case here.
Do you alloc/init the mutable array in the init method?
I have an application where A View Controller (A)is called twice in close succession. Now each time it is called, an NSString object is created, and I need this value to be stored in an NSMutableArray that is a public property of ANOTHER View Controller (B).
In A, I create an instance of the second View Controller (B), and using that instance, add the NSString objects into the NSMutableArray which I've created as a public property. Later, when I am inside View Controller B and print the contents of the NSMutableArray property, the array is empty. Why? Here is the code that is inside View Controller A:
-(void)viewDidLoad {
ViewControllerA *aVC = [[ViewControllerA alloc] init];
if (aVC.stringArray == nil) {
aVC.stringArray = [[NSMutableArray alloc] init];
}
[aVC.stringArray addObject:#"hello"];
[aVC.stringArray addObject:#"world"];
for (NSString *wow in aVC.stringArray) {
NSLog(#"The output is: %#", wow);
}
}
Inside my View Controller B class, I have the following code:
- (IBAction)buttonAction:(UIButton *)sender {
NSLog(#"Button selected");
for (NSString *test in self.stringArray) {
NSLog(#"Here are the contents of the array %#", test);
}
}
Now the buttonAction method gets called, as I do see the line Button selected in the system output, but nothing else is printed. Why? One thing I want to ensure is that View Controller A is called twice, which means I would like to see in the output, "Hello World", "Hello World" (i.e. printed twice), and not "Hello World" printed just once.
The other thing I wish to point out is that View Controller B may not be called at all, or it may be called at a later point in time. In any case, whenever View Controller B is called, I would like to have the values inside the array available, and waiting for the user to access. How do I do this?
Your approach is not ideal, potentially leading to a memory cycle, with two objects holding strong pointers to each other.
You can instead achieve your goal in two ways;
Delegate Protocol
This method allows you to set delegates and delegate methods to pass data back and forth between view controllers
in viewControllerA.h
#protocol viewControllerADelegate <NSObject>
- (void)addStringToNSMutableArray:(NSString *)text;
#end
#interface viewControllerA : UIViewController
#property (nonatomic, weak) id <viewControllerADelegate> delegate;
in viewControllerB.m
// create viewControllerA class object
[self.viewControllerA.delegate = self];
- (void)addStringToNSMutableArray:(NSString *)text
{
[self.mutableArray addObject:text];
}
in viewControllerA.m
[self.delegate addStringToNSMutableArray:#"some text"];
Utility Classes
Alternatively you can use a utility class with publicly accessible methods (and temporary data storage). This allows both viewController classes to access a shared data store, also if you use class methods, you don't even need to instantiate the utility class.
in XYZUtilities.h
#import <Foundation/Foundation.h>
#interface XYZUtilities : NSObject
+ (void)addStringToNSMutableArray;
#property (strong, nonatomic) NSMutableArray *array;
#end
in XYZUtilities.m
+ (void)addStringToNSMutableArray
{
NSString *result = #"some text";
[self.array addObject:result];
}
+ (NSArray)getArrayContents
{
return self.array;
}
in viewControllerA.m
NSString *stringFromObject = [XYZUtilities addStringToNSMutableArray];
in viewControllerB.m
self.mutableArray = [[NSMutableArray alloc] initWithArray:[XYZUtilities getArrayContents]];
I'm not sure what kind of a design pattern you are trying to follow but from the looks of it IMHO that's not a very safe one. However, there are many, many ways this could be accomplished.
One thing though, you said that View Controller B may never get allocated and if it is alloc-ed, it will be down the road. So you can't set a value/property on an object that's never been created.
Since you already aren't really following traditional patterns, you could make a static NSMutableArray variable that is declared in the .m of your View Controller B Class and then expose it via class methods.
So it would look like this:
viewControllerB.h
+(void)addStringToPublicArray:(NSString *)string;
viewContrllerB.m
static NSMutableArray *publicStrings = nil;
+(void)addStringToPublicArray:(NSString *)string{
if (publicStrings == nil){
publicStrings = [[NSMutableArray alloc]init];
}
if (string != nil){
[publicStrings addObject:string];
}
}
Then it would be truly public. All instances of view controller B will have access to it. This, of course is not a traditional or recommended way of doing it—I'm sure that you will have many replies pointing that out ;).
Another idea would be to use a singleton class and store the values in there. Then, when or if view controller B is ever created, you can access them from there.
I'm having a lot of difficulty getting core data to work in my application. I'm perfectly comfortable with core data in a table view controller and even with core data in a single view application. I'm unable however to get my program to function properly in with multiple view controllers.
I've read through Zarra's core data book and bought pro core data for iOS and have gone through the projects listed but every one of them is used in a TableView controller. with the exception of the shapes application in pro core data for iOS.
Does anyone know of any examples (code or tutorials) that would demonstrate how to do a program with multiple view controllers and core data?
What I would like to do is have buttons on the first (instead of tableview cells) that will segue to the the next viewController. On the second view controller I would like that information populated with information from the set of the first entity,
so I have something like this so far where the first entity is:
House
houseName (attribute)
occupants (relationship)
Person
personName (attribute)
household (relationship)
occupants <-->>household (one to many )
{
...
int i = //house selected on previous view controller;
NSManagedObject *people = [[self sortOccupants] objectAtIndex:i];
textField01.text = [NSString stringWithFormat:#"%#",[[people valueForKey:#"personName"]description]];
}
the sort occupants looks like this:
-(NSArray *)sortOccupants
{
NSSortDescriptor *sortPeopleInHouse = [[NSSortDescriptor alloc] initWithKey:#"personName" ascending:YES];
NSArray *sortDescriptors = [NSArray arrayWithObjects:sortPeopleInHouse, nil];
return [[(NSSet *)[house valueForKey:#"occupants"] allObjects] sortedArrayUsingDescriptors:sortDescriptors];
}
Any Ideas would be great but if you can point me to sample code that would show this I would be most appreciative.
Thanks,
The way I would approach this is to generate NSManagedObject subclasses for your entities (makes it much more readable and type-safe).
Then, I would create a new init method in the second view controller. initWithHouse:(House *)house or something:
#property (nonatomic, strong) House * currentHouse;
#property (nonatomic, strong) NSArray * sortedOccupants; // Array of People objects
-(id) initWithHouse:(House *)house
{
if (self = [super init])
{
// Managed Object Context available from [currentHouse managedObjectContext]
currentHouse = house;
sortedOccupants = [self sortOccupants:house.occupants];
}
return self;
}
-(NSArray *)sortOccupants: (NSSet *)occupants
{
NSSortDescriptor *sortPeopleInHouse = [[NSSortDescriptor alloc] initWithKey:#"personName" ascending:YES];
NSArray *sortDescriptors = [NSArray arrayWithObjects:sortPeopleInHouse, nil];
return [occupants allObjects] sortedArrayUsingDescriptors:sortDescriptors];
}
Hope that helps.
Maybe the key is that each view controller should have
#property (nonatomic, strong) NSManagedObjectContext *managedObjectContext;
in its header.
Before you push a view controller you give it a managed object context. In this new view controller you can have your typical methods for searching and saving the context.
You start with a home view controller, which is really a list of people. You select a person and launch a person view controller. You should pass a managed object context and an instance of Person to this person view controller.
I am wondering what the correct way is to make a copy of an object defined in the app delegate or a singleton object. In short, I am making an app which requires a user to login. This login view is just a modal view controller on top of the 'real' app, which consists of a tabbarcontroller, plus some tableview controllers. After a successful login, there is send a data request to a remote server, and the modal view controller is dismissed, revealing the tabbar controller and table views holding the XML data. To parse the incoming data, I have created a singleton object named DataParser, which has interface
...
#interface DataParser : NSObject {
// Data objects that hold the data obtained from XML files
NSMutableDictionary *personnel;
NSMutableDictionary *schedule;
NSMutableDictionary *today;
}
#property (nonatomic, retain) NSMutableDictionary *personnel;
#property (nonatomic, retain) NSMutableDictionary *schedule;
#property (nonatomic, retain) NSMutableDictionary *today;
...
Now in these dictionaries I store (mutable) dictionaries and arrays holding NSString objects with the parsed XML data. Since I do not want to modify these original objects holding the parsed data (that is to say, I only want to modify them at the login stage, but not in any of the tableview controllers), I am creating a new dictionary object which holds a copy of the content of one of the dictionaries above in each tableview controller. So for instance, in the loadView of a view controller called ScheduleViewController I have
...
#interface ScheduleViewController : UITableViewController {
NSDictionary *copyOfSchedule;
}
#property (nonatomic, retain) NSDictionary *copyOfSchedule;
...
#end
#implementation ScheduleViewController
#synthesize copyOfSchedule;
- (void)loadView {
[super loadView];
DataParser *sharedSingleton = [DataParser sharedInstance];
self.copyOfSchedule = [NSDictionary dictionaryWithDictionary:sharedSingleton.schedule];
}
...
Now this seems to work fine. The only difficulty arises however, when the user 'logs out', which entails popping the login modal view controller back on the stack. When the user presses the login button again, then a new XML data request is send to the server and the dictionaries in the singleton object get refreshed with the (new) data (I check if they contain any data, if so I call removeAllObjects before filling them up again with newly parsed data). At this point the dictionaries in all view controllers should be updated too, however I am not quite sure how to go about this the right way. I have noticed that loadView is not always called again in this case and so to this end I have added the same code as above in loadView to every viewWillAppear method. After navigating back and forth between the different views or navigating back and forth between child views of a tableview a couple of times, I receive an EXC_BAD_ACCESS error however. I suspect this has to do with not properly retaining the copies of the original dictionaries, but I don't seem to be able to find a solution around this. Instead of using dictionaryWithDictionary, which I suspect is not the right way to go anyway, I also tried a different approach, where instead of using objects of type NSDictionary in ScheduleViewController I use NSMutableDictionary. So:
...
#interface ScheduleViewController : UITableViewController {
NSMutableDictionary *copyOfSchedule;
}
#property (nonatomic, retain) NSMutableDictionary *copyOfSchedule;
...
#end
#implementation ScheduleViewController
#synthesize copyOfSchedule;
- (void)loadView {
[super loadView];
DataParser *sharedSingleton = [DataParser sharedInstance];
self.copyOfSchedule = [[NSMutableDictionary alloc] initWithDictionary:sharedSingleton.schedule];
}
- (void)viewWillAppear {
DataParser *sharedSingleton = [DataParser sharedInstance];
[self.copyOfSchedule removeAllObjects];
[self.copyOfSchedule addEntriesFromDictionary:sharedSingleton.schedule];
[self.tableView reloadData];
}
...
But this doesn't get rid of the EXC_BAD_ACCESS errors. To make a very long story short: what would be the best way to go about making independent copies of objects defined in a singleton object or app delegate and which can be dynamically updated at request? Since I am already rather into the project and lots is going on, I realize that my question may be a bit vague. Nonetheless I hope there is somebody who could enlighten me somehow.
Deep copies are often made recursively. One way to do it would be to add -deepCopy methods to NSDictionary and NSArray. The dictionary version might go like this:
- (NSDictionary*)deepCopy
{
NSMutableDictionary *temp = [self mutableCopy];
for (id key in temp) {
id item = [temp objectForKey:key];
if ([item respondsToSelector:#sel(deepCopy)] {
// handle deep-copyable items, i.e. dictionaries and arrays
[temp setObject:[item deepCopy] forKey:key]
}
else if ([item respondsToSelector:#(copy)]) {
// most data objects implement NSCopyable, so will be handled here
[temp setObject:[item copy] forKey:key];
}
else {
// handle un-copyable items here, maybe throw an exception
}
}
NSDictionary *newDict = [[temp copy] autorelease];
[temp release]
return newDict;
}
I haven't tested that, so be a little careful. You'll want to do something similar for NSArray.
Note that views are not copyable.
It is quite a typical pattern that you build an array or dictionary with some code, so clearly it must be mutable while you add bits to it, and when you're done you don't want it ever to change. To do this:
Have a property like
#property (...) NSArray* myArray;
When you calculate the contents of myArray, use a mutable array to build it, like
NSMutableArray* myMutableArray = [NSMutableArray array];
When you're done building the array, just use
self.myArray = [NSArray arrayWithArry:myMutableArray];