How to push from a UITableViewController to a UIViewController using NSDictionary? - ios

I have tried to push to a UIViewController using an Array:
self.cities=#[#"New York", #"Chicago", #"Los Angeles", #"Miami"];
and then in my didSelectRowAtIndexPath:
[self.navigationController pushViewController:NewYorkViewController animated:YES];
but now all the items in the array push to NewYorkViewController. I believe an NSDictionary would work
NSDictionary *cities=#[#{#"city":#"NewYork",#"viewController":NewYorkViewController}, #{#"city":#"Chicago",#"viewController":ChicagoViewController}, #{#"city":#"LosAngeles",#"viewController":LosAngelesViewController}, #{#"city":#"Miami",#"viewController":MiamiViewController}];
However I'm a noob and have never worked with NSDictionary. If you would be so kind to explain how i would go about doing this. If you know a way using array's that would work too.
ps.
I don't use storyboards and could you also explain what I would do in my cellForRowAtIndexPath: and numberOfRowsInSection:
Thanks

I am guessing you want to transfer your data from the UITableViewController to NewYorkViewController. The usual way to do this is having a property in your NewYorkViewController say an array or whatver you want.
#property (nonatomic,retain) NSMutableArray *itemsArray;
and in didSelectRowAtIndexPath while creating NewYorkViewController's object
NewYorkViewController *newyorkObject = [NewYorkViewController alloc]init];
newyorkObject.itemsArray = self.cities;
[self.navigationController pushViewController:newyorkObject animated:YES];
and in viewDidLoad of NewYorkViewController add this:
NSLog(#"items array: %#",self.itemsArray); //to check whether you got what you passed from the previous class.

I am using the Windows System now, so the following code may contain errors.
Use reflection to get the IOS Class
And then use this to alloc an object and operate it
You can use [Object class] or NSClassFromString ("Class Name") to get type of class
like this:
NSString * ClassName = # "yourClassName";
Class * tempClass = NSClassFromString (ClassName);
//if your class is subclass of UIViewController
UIViewController * tempObj = [[tempClass alloc] init];
//operating it
[self.navigationController pushViewController: tempObj animated:YES];
[tempObj release];
For your code you can change it like this:
//NSDictionary =#{#"key",#"value",....}
NSDictionary *citieone=#{#"NewYork",[NewYorkViewController Class]}
NSDictionary *citietwo = #{#"Chicago",NSClassFromString(#"ChicagoViewController")};
//other citys
NSArray* cities = #{citieone,citietwo,...};
//in UITableView
NSString* cityName = [[[citis objectForIndex:index] allKeys] objectAtIndex:0];
Class* controllerClass = [[[citis objectForIndex:index] allValues] objectAtIndex:0];
// operating it
UIViewController * controller= [[controllerClass alloc] init];
[self.navigationController pushViewController: controller animated:YES];
[controller release];

I did something similar with my app, but I was using NSArray instead of NSDictionary, but the logic is similar IMHO.
I created a NSArray with my objects, and a method to return their count and also the name at index. This is so that when I want to get a particular object from the array I can access it quicky. I have changed my code to accept the NSDictionary so that you can use.
In order to create the number of rows in the UITableView, I created a method that returns the number of rows when called.
-(NSInteger) numberOfCities
{
NSArray * allKeys = [mutableDictionary allKeys];
return [allKeys count];
}
This will be called in the
- (NSInteger) tableView: (UITableView*) tableView numberOfRowsInSection: (NSInteger) section
{
return [self numberOfCities];
}
so that the UITableView will have the exact number of rows as the NSDictionary
In order to return the name at a particular index so that you can display that name in the UITableViewCell, I created this method
-(NSString*) cityNameAtIndex: (NSInteger) index
{
return NSString* cityName = [[[mutableDictionary objectForIndex:index] allKeys] objectAtIndex:index]; ;
}
This method can be called in the
- (UITableViewCell*) tableView: (UITableView*) tableView cellForRowAtIndexPath: (NSIndexPath*) indexPath
to assign the different city names to different cells in the table.
Then in the delegate
- (void) tableView: (UITableView*) tableView didSelectRowAtIndexPath: (NSIndexPath*) indexPath
you can call this method and push to the different viewControllers
UIViewController* selectedController = nil ;
NSString* nameOfCity = [self cityNameAtIndex: indexPath.row] ;
if( [nameOfCity isEqualToString:#"New York"] )
selectedController = [[NewYorkViewController alloc] init];
else if( [nameOfCity isEqualToString:#"Chicago"] )
selectedController = [[ChicagoViewController alloc] init];
else if( [nameOfCity isEqualToString:#"Miami"] )
selectedController = [[MiamiViewController alloc] init];
else if( [nameOfCity isEqualToString:#"Los Angeles"] )
selectedController = [[LosAngelesViewController alloc] init];
To do this, you will have to create the different viewControllers (Chicago, New York, Los Angeles, Miami) beforehand, but if you have already created them and left them as an object in the NSDictionary, you can use the nameOfCity to grab the viewController object within the NSDictionary that belongs to that city, and then assign the selectedController to that viewController.
But if you are only using the NSDictionary to store the names of the cities, I think that NSArray will be a better option. I created mine in the appDelegate and the names within the NSArray are #define in my class called global.h as I needed the NSArray and names in many different viewControllers, so if you need that as well, you can do the same.
Hope this helps! :)

Related

Update Label of a Custom Subclass of UITableViewCell after navigation return?

I am trying to update the score of one of the labels on my custom cell after returning from a push navigation.
In my parent UITableViewController I have the code:
In ViewDidLoad
//These are mutable strings
self.disciplineScoreString1 = [NSMutableString stringWithFormat:#""];
self.disciplineScoreString2 = [NSMutableString stringWithFormat:#""];
self.disciplineScoreString3 = [NSMutableString stringWithFormat:#""];
self.disciplineScoreArray = [[NSMutableArray alloc] init];
[self.disciplineScoreArray addObject:self.disciplineScoreString1];
[self.disciplineScoreArray addObject:self.disciplineScoreString2];
[self.disciplineScoreArray addObject:self.disciplineScoreString3];
So far so good.
In cellForRowAtIndexPath
//this is the custom subclass of UITableViewCell
DayTableViewCell *cell = (DayTableViewCell *) [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
//this is the custom label
cell.disciplineScoreLabel.text = [self.disciplineScoreArray objectAtIndex:indexPath.row];
return cell;
Still so far so good. Right now the label in each of the 3 cells is blank.
I now segue to a UIViewController from the first cell and I return from the child UIViewController successfully setting the string value of self.disciplineScoreString1 to #"10"which I NSLog'ged in the parent UITableViewController.
How do I update the label for the first cell now? I have tried reload data in ViewWillAppear but its not working.
Thankyou
EDIT
This is the code in the Child ViewController
In viewWillDisappear:
[super viewWillDisappear:animated];
[self calculateDisciplineScore];
NSInteger currentVCIndex = [self.navigationController.viewControllers indexOfObject:self.navigationController.topViewController];
DisciplineTableViewController *parent = (DisciplineTableViewController *)[self.navigationController.viewControllers objectAtIndex:currentVCIndex];
parent.disciplineScoreString1 = self.disciplineScoreText;
You must be changing the string, instead of modifying it... consider this example
NSMutableArray *array = [NSMutableArray array];
NSMutableString *disciplineScoreString1 = [NSMutableString stringWithFormat:#"original string"];
[array addObject:disciplineScoreString1];
[disciplineScoreString1 appendString:#" hello"]; // OK.. you're modifying the original string
NSLog(#"%#", [array objectAtIndex:0]);
disciplineScoreString1 = [NSMutableString stringWithFormat:#"new string"]; // WRONG!!
NSLog(#"%#", [array objectAtIndex:0]);
The ouput is:
2014-02-15 06:36:46.693 Hello[19493:903] original string hello
2014-02-15 06:36:46.694 Hello[19493:903] original string hello
The second example is wrong because you're creating a new string, and the object in the array is still pointing to the old original string.
EDIT:
// try this
[parent.disciplineScoreString1 replaceCharactersInRange:NSMakeRange(0, parent.disciplineScoreString1.length) withString:self.disciplineScoreText];
// instead of
parent.disciplineScoreString1 = self.disciplineScoreText;
Your current approach is quite rigid and not very standard. You would normally set up some kind of delegate to pass data from a child to a parent. It is very rarely correct that a child should know how to reach into the parent's innards and change it's data.
I would personally start with something like this
#interface ChildViewController : UIViewController
#property (nonatomic, copy) void (^onCompletion)(ChildViewController *viewController, NSString *disciplineText);
#end
Now I'm assuming you are dismissing by just calling [self.navigationController popViewControllerAnimated:YES] from within the child? This is a little odd as it means that the childViewController can now only ever be presented in a navigationController, this makes it less reusable.
What I would do is at the point where you normally call popViewControllerAnimated: I would call the completion instead
if (self.onCompletion) {
self.onCompletion(self, self.disciplineText);
}
Now the object who provides the onCompletion can decide how this controller get's removed and it's told about the data that we finished with, which enabled the controller to do what it wants with it.
In your case the parent controller would provide the completion as it knows how the child is presented so it will know how to dismiss it. It also know that it may want to do something with the data the child finished with
// Where you present the child
childViewController.disciplineText = self.self.disciplineScoreArray[index];
__weak __typeof(self) weakSelf = self;
childViewController.onCompletion = ^(ChildViewController *viewController, NSString *disciplineText){
weakSelf.disciplineScoreArray replaceObjectAtIndex:index withObject:disciplineText];
[self.navigationController popViewControllerAnimated:YES];
[weakSelf.tableView reloadRowsAtIndexPaths:#[ [NSIndexPath indexPathForRow:index inSection:0] ]
withRowAnimation:UITableViewRowAnimationFade];
};

How can I use an NSDictionary and map my array of strings to each UIViewController class

How can I use an NSDictionary and map my array of strings to each UIViewController class, and then use objectForKey and get the correct view controller for each key?
I am trying to have each item of my array push to its own view controller.
My array is:
NSArray *Cities:#["New York", "Chicago", "Miami"];
and i want the item #"New York" to push to NewYorkViewcontroller and #"Chicago" to chicagoviewvontroller etc.
You could consider having a different segue for each item, and in your dictionary store the segue identifier.
However, this will quickly get out of hand if you have more than a few items, in which case you are better off having a generic view controller with custom setup methods rather than doing it this way.
NSDictionary* mapping = #{"New York”: [NewYorkViewController class], … };
NSString* chosenCity;
Class vcClass = mapping[chosenCity];
UIViewController* vc = [[vcClass alloc] init];
Pseudo-code:
NSDictionary * myDictionary = [NSDictionary dictionaryWithObjects: newYorkViewController, chicagoviewcontroller forKeys: Cities[0], Cities[1]];
[self pushViewController: [myDictionary objectForKey:#"New York"] animated:YES];
you can use
#[#{#"title":#"New York",#"vc_class":[NewYorkViewController class]}, …];

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.

Passing NSArray from DidSelectRow of a table to another UITableview

I am trying to pass an array from one table ,and populate it in another table.The parent table is place upon a UINavigationController say "mainNavig".The child table is placed in another ViewController of name "SongsofAlbum".My didSelectRowAtIndexPath of parent table is as follows,
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
albumName = [eliminateDupe objectAtIndex:indexPath.row];
temp = [dictforalbum allKeysForObject:albumName ];
songs = [NSMutableArray arrayWithCapacity:[temp count]];
for (NSString *filename in temp) {
[songs addObject:[[filename lastPathComponent] stringByDeletingPathExtension]];
}
NSLog(#"songs are %#",songs);
songObj = [[SongsofAlbum alloc]initWithNibName:#"SongsofAlbum" bundle:nil];
[mainNavig pushViewController:songObj animated:YES];
songObj.albumname = albumName;
songObj.songArray = songs;
NSLog(#"the song object array is %# ",songObj.songArray)
}
The nslog of songObj.songArray returns the data in the above method .But the problem I face is ,when I call this songArray in the child view controller it returns NULL . I even property synthesized the arrays. Any suggestions?
But the problem I face is ,when I call this songArray in the child
view controller it returns NULL . I even property synthesized the
arrays.
In other ViewController you are creating a new instance of this class. The new instance will be NULL.
You do not need to create new instance infact you need to use this same object's value from there.
You can do this thing by: How to make button in child view to update information in its parent view?
You can take your songs array in AppDelegate and then instead assigning songObj.songArray = songs you can assign it in your SongsofAlbum class's viewDidLoad like
AppDelegate *app;
- (void)viewDidLoad
{
[super viewDidLoad];
app=(AppDelegate *)[[UIApplication sharedApplication]delegate];
songarray= [[NSMutableArray alloc]initWithArray:app.songs];
}
Hope this will work.

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