Passing a MutableArray between ViewControllers - ios

BookingDocumentsViewController *bdVc = [self.storyboard instantiateViewControllerWithIdentifier:#"BookingDocs"];
bdVc.orId = rl_id;
bdVc.docsArray = self.documentsArray;
[self.navigationController pushViewController:bdVc animated:YES];
I have Above code snippet. I'm trying to load a new viewcontroller and assign its Mutable Array (docsArray) object to current view's mutableArray (documentsArray <=this is not nil)
Whenever I execute above code I get EXC_BAD_ACCESS error.
but if I comment the 3rd line. It works but I can't get my array to the new view. I even tried with [[NSMutableArray alloc] initWithArray:self.documentsArray]; this doesnt work either.
But if I use bdVc.docsArray =[[[NSMutableArray alloc] init]; it works but again I can't get my mutable array to the new view.
Edit:
However 2nd line has NSString values. And they can be passed without a problem.
What am I doing wrong here?
I'm not getting any errors in console, instead I get this.

Maybe consider using a Segue. It instantiates the destination viewcontroller for you. Then in your source view controller implement
- (void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
Get a reference to your destination viewcontroller and set its data.
BookingDocumentsViewController *bdVc = [segue destinationViewController];
bdVc.docsArray = self.documentsArray;

in BookingDocumentsViewController.h
#property(nonatomic, retain)NSmutableArray *docsArray;
You do can do in your BookingDocumentsViewController.m:
#synthesize docsArray;
- (void)viewDidLoad
{
NSmutableArray *array = [NSmutableArray alloc]initWithArray:docsArray];
[super viewDidLoad];
}
then when you are pushing the view
BookingDocumentsViewController *bdVc = [self.storyboard instantiateViewControllerWithIdentifier:#"BookingDocs"];
bdVc.orId = rl_id;
bdVc.docsArray = self.documentsArray;
[self.navigationController pushViewController:bdVc animated:YES];
[bdVc Release];

I think I found the issue. A very basic mistake. In bdVc's viewDidLoad I had the following line,
NSLog(#"Booking Documents viewDidLoad : %#",self.docsArray.count);
This was causing the error. that %# instead of %d. I wonder why xcode didn't show proper reason for the error.
Thank you all for the help. :)

Related

Passing data with delegate but still nil why?

I made 2 viewcontrollers and implemented on tabbar controller. I passed some data from A vc to B vc with using delegate. When I checked the log it showed me correct value. But when I moved to B vc the value I passed was nil. (the value is for tableview.) Here is my code.
in A vc
-(void)passData {
NSMutableDictionary *infoDic = [[NSMutableDictionary alloc] init];
[infoDic setObject:url forKey:#"file_url"];
[downloadArr addObject:fileInfoDic];
Bvc getDataFromA:downloadArr];
[Bvc reloadTableView];
}
in B vc
-(void)getDataFromA:(NSMutableArray *) downloadArr{
self.downloadArr = [downloadArr mutableCopy];
NSLog(#"my download list%#", self.downloadArr); // This time was ok.
}
- (void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
NSLog(#"Array status %#", self.downloadArr);//This time it showed me nil
[self.tableView reloadData];
}
To have the array printed inside viewWillAppear without nil
NSLog(#"Array status %#", self.downloadArr);//This time it showed me nil
you need to give it a value before you show bVC from aVC , whatever you use present/segue/push , also don't forget to declare it as strong , you need to do this
bvc = [[self.tabBarController viewControllers] objectAtIndex:1];
[bvc loadViewIfNeeded];
[bvc getDataFromA:downloadArr];
Please check the way you move to B-VC and whether the instance of (B-VC) you used in the A -VC is same to self in the B-VC “viewWillAppear” method.
It seems when u move to B-VC, A new instance has been created.
Or you can implement the 'setDownloadArr' method, log the value and show when the value become nil.

Problems reloading data in a tableView

I am trying to reload data in a tableview based on a users account permissions whenever they log in.
The two classes involved in this are:
mainViewController and menuViewController
Currently I am able to use
[self.tableView reloadData];
To reload the data when called within the viewWillAppear method. Which is no good for me since the user hasn't logged in when the view loads so there is no data to populate the table at this point.
I have created a method called populateTable in menuViewController.h which I am calling in the mainViewController.m file on button press using the following;
(IBAction)Reload:(id)sender {
menuViewController *mvc = [[menuViewController alloc]init];
[mvc populateTable];
}
This seems to work correctly as I have an NSLog within the populateTable method which executes. However the reloadData does not work.
Here is my populateTable method;
-(void)populateTable {
self.section1 = [NSMutableArray arrayWithObjects:#"test settings", #"test", #"test",#"Users and access",#"No-track IPs", nil];
self.section2 = [NSMutableArray arrayWithObjects:#"Rules", #"Channels",#"Goals",#"Pages", nil];
self.menu = [NSMutableArray arrayWithObjects:self.section1, self.section2, nil];
[self.tableView reloadData];
NSLog(#"Reloading data");
}
Can you guys help me out here, I have been staring at this all day and getting nowhere, thanks!
From my experience this is likely a problem with timing - the IBOutlet of self.tableView is not ready when you call reloadData on it (add an NSLog and see for yourself - it is nil when called).
To solve this, the populateTable method must be called within the UIViewController's viewDidLoad method. This guarantees that the outlets are not nil and that everything is ready for your data population.
Also, you should not instantiate your MenuViewController with [[MenuViewController alloc] init] but using the storyboard's instantiateViewControllerWithIdentifier.
Your problem is this line,
menuViewController *mvc = [[menuViewController alloc]init];
This creates a new instance of menuViewController, not the one you see on screen. You need to get a reference to the one you have, not create a new one. How you get that reference depends on how, when, and where your controllers are created.

Adding to a NSMutableArray

I can't find an obvious answer this. I'm trying to add things to an array, so I assume I need to use an NSMutableArray
I have a ViewController (CVDownload) and and a TableViewController(CVTableViewController). The NSMutableArray is declared in CVTableViewController.h
#property (strong, nonatomic) NSMutableArray *cvFiles;
I then try to add a string to it in the CVDownload.m
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
CVTableViewController *controller = (CVTableViewController *)segue.destinationViewController;
[controller.cvFiles addObject:(#"sdsd")];
}
That doesn't work. I'm assuming because I haven't initialised the array. I've tried initialising the array in CVDownload.m
NSMutableArray *cvFiles = [NSMutableArray array];
but that doesn't work either. In debug, the array is still nil. I don't understand where I'm going wrong.
Basically, my goal is to have an array in CVTableViewController that is used to populate a Table, and I want to be able to add to the array in CVDownload. Is there something I'm missing? Does NSArray have a similar method to NSString's stringByAppendingString?
In the init method of CVTableViewController write this:
self.cvFiles = [NSMutableArray new];
CVTableViewController *controller = (CVTableViewController *)segue.destinationViewController;
if(nil == controller.cvFiles)
{
controller.cvFiles = [[NSMutableArray alloc] init];
}
[controller.cvFiles addObject:(#"sdsd")];
Where are you initializing the array? Are you sure that it is created before prepareForSegue: is called?
// Edit: I ask because, as confirmed in the comments, the array is initialized after prepareForSegue: is called. The fix, as mentioned there, is to initialize the array in -awakeFromNib instead of -viewDidLoad.

NSArray losing value at the end of method

I have a project I am working on, to learn some more about JSON and restkit. It all is working great, however I am having trouble with an array losing it's values.
This is the last method that is executed in my network request.
SHRetrieveStoresWS.m
- (void)objectLoader:(RKObjectLoader *)objectLoader didLoadObjects:(NSArray *)objects
{
self.stores = [[NSArray alloc] initWithArray:objects];
StoresViewController *viewController = [[StoresViewController alloc] init];
[viewController didLoadObjects:objects];
for (Store *aStore in stores) {
NSLog(#"%#", [aStore longName]);
}
}
Which calls this method in my view controller.
StoresViewController.m
#property (strong, nonatomic) NSArray *data;
- (void)didLoadObjects:(NSArray *)aArray
{
NSLog(#"%d", aArray.count);
self.data = [[NSArray alloc] initWithArray:aArray];
NSLog(#"%d", data.count);
[self.tableView reloadData];
}
The values are correct when I ask for the values within this method, but the array shows 0 objects immediately afterwards. Am I missing something here?
I am later checking the value with this method.
- (IBAction)pushMe:(id)sender
{
NSLog(#"Data: %d", self.data.count);
}
You should pass the data in the segue...
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
if ([segue.identifier isEqualToString: #"MY_IDENTIFIER"]){
StoresViewController *viewController = segue.destinationViewController;
[viewController didLoadObjects: objects];
}
}
That should work for you! Just change MY_IDENTIFIER to whatever the identifier of your segue is.
StoresViewController is initialised as a local variable which is only accessible in the method that it was declared (aka objectLoader). After objectLoader has completed, the local variable is no longer valid.
The problem is most likely that you're creating multiple instances of StoresViewController instead of giving your network controller a reference to the original instance.
You can demonstrate it to yourself by printing out self in -viewDidLoad and again in didLoadObjects:. You'll see that the pointer addresses are different.
This line is the culprit:
StoresViewController *viewController = [[StoresViewController alloc] init];
Instead of instantiating StoresViewController again, add a property to your SHRetrieveStoresWS class and use it to hold a reference to your view controller.
#property (strong) StoresViewController *viewController;
You'll need to set that property before -didLoadObjects: is invoked.

Detail View filled with plist information

I've been following a tutorial on iOS development - specifically drill-down UITableViews. I have my own custom plist established, but I can't seem to get the DetailViewController to populate with my plist information. I could really use some help here, I'm a bit over my head!
edit: Here's some details...
The app works through a plist-populated RootViewController, which is a UITableView. When there aren't any children left in the plist, it changes to a Detail view:
AppDelegate.m
NSDictionary *tempDict = [NSDictionary dictionaryWithContentsOfFile:[[NSBundle mainBundle] pathForResource:#"Data" ofType:#"plist"]];
self.data = tempDict;
RootViewController.m
- (void)viewDidLoad
{
[super viewDidLoad];
if(CurrentLevel == 0) { // At the 'root' of the plist
//Initilalize our table data source
NSArray *tempArray = [[NSArray alloc] init];
self.tableDataSource = tempArray;
AppDelegate *AppDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
self.tableDataSource = [AppDelegate.data objectForKey:#"Rows"];
self.navigationItem.title = #"PedalTome";
}
else
self.navigationItem.title = CurrentTitle;
}
later on...
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
//Get the dictionary of the selected data source.
NSDictionary *dictionary = [self.tableDataSource objectAtIndex:indexPath.row];
//Get the children of the present item.
NSArray *Children = [dictionary objectForKey:#"Children"];
if([Children count] == 0) {
DetailViewController *dvController = [[DetailViewController alloc] initWithNibName:#"DetailView" bundle:[NSBundle mainBundle]];
[self.navigationController pushViewController:dvController animated:YES];
}
else {
//Prepare to tableview.
RootViewController *rvController = [[RootViewController alloc] initWithNibName:#"RootViewController" bundle:[NSBundle mainBundle]];
//Increment the Current View
rvController.CurrentLevel += 1;
//Set the title;
rvController.CurrentTitle = [dictionary objectForKey:#"Title"];
//Push the new table view on the stack
[self.navigationController pushViewController:rvController animated:YES];
rvController.tableDataSource = Children;
}
}
My DetailViewController.m is empty, with the exception of a placeholder self.navigationController.title.
If I'm understanding correctly, I need to pass information from RootViewController to DetailViewController - the location and implementation of the plist, the index level (is that what it's called) in the plist, and the string inside that index level (under the key Detail).
At this point, any progress is amazing progress. Thanks in advance.
You can pass whatever information you need to your DetailViewController by setting up a synthesized property in your DetailViewController, and then passing your data to it inside your if-block.
For example, in your DetailViewController.h you would have the following (without ARC):
#property (retain, nonatomic) NSDictionary *myAwesomeDictionary;
Or, with ARC enabled:
#property (strong, nonatomic) NSDictionary *myAwesomeDictionary;
Then in DetailViewController.m you would have the following:
#synthesize myAwesomeDictionary;
Then you would change your code block to the following:
if([Children count] == 0) {
DetailViewController *dvController = [[DetailViewController alloc] initWithNibName:#"DetailView" bundle:[NSBundle mainBundle]];
[dvController setMyAwesomeDictionary:dictionary];
[self.navigationController pushViewController:dvController animated:YES];
}
This assumes that the NSDictionary called dictionary that you created a few lines above is the data that you'd like to show in your DetailViewController.
Then in your DetailViewController's viewDidLoad: method you can access that dictionary using self.myAwesomeDictionary and do whatever you need to do with it.
Disclaimer:
Two things seem to go against Apple's code style standards in your code:
Your AppDelegate stores your model (your plist). - Apple says that you shouldn't crowd your AppDelegate with global data/logic. In general, only write code that pertains specifically to a class, in that specific class.
You aren't parsing your plist into custom objects. - This makes it hard to code because you constantly have to figure out what your generic Array and Dictionary objects represent, and make your code totally unreadable for other people.
Some of your instance variable names are capitalized. For example, NSArray *Children should be NSArray *children and CurrentLevel should be currentLevel. Only Class names have the first letter capitalized.
Check out http://jlawr3nc3.github.com - specifically my CompanyRecords example code for information on how to make a class and FunWithArrays for how to parse a plist into custom objects. MusicLibraryiOS then delves into how to take a plist, parse it into custom objects, and then display it in a UITableView along with a detail view.
Table View Specifier May do what you need.
Specified Table View is an iOS table view that has its contents specified
via a plist file. Its purpose is largely demonstrative but is also designed
to be used in a live product (useful for credits pages). Can be used with
iOS version 4.2 and above.
A dig through their code will most likely be enlightening.

Resources