I have a UIViewController in my iOS application that displays a table that is derived from an NSMutableArray. The cells in this table each refer to a unique UIViewController that is called when the user makes a selection. What I am trying to do in my "didSelectRowAtIndexPath:" method is to dynamically create the UIViewController via an NSMutableDictionary that contains keys that match the values in the NSMutableArray that the table is built from, as well as values that contain the corresponding Class names for the respective UIViewController that needs to be called. Because the list is rather long, I figure I need to do this using a for loop, but I am a bit confused as to how to do it. My NSMutableDictionary looks like this:
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
[dict setObject:#"aViewController" forKey:#"SelectionA"];
[dict setObject:#"bViewController" forKey:#"SelectionB"];
[dict setObject:#"cViewController" forKey:#"SelectionC"];
and my NSMutableArray that is the basis for my TableView looks like this:
NSMutableArray *myArray = [[NSMutableArray alloc] initWithObjects:#"SelectionA", #"SelectionB", #"SelectionC",...,nil];
How would I obtain a reference to the value inside the cell, and then construct a for loop that would dynamically create the correct viewController that corresponds to the selection made by the user from the tableView, and then take the user to that viewController via the navigationController?
Thanks in advance to all who reply.
Its not a good idea to create many ViewControllers, you should create one ViewController,
and pass the value of they tableCell to it. In other words, you change the data modell of the ViewController, by selecting the cell. But you will present the Same ViewController.
Only in the case that your cells coreesponmd to different types (e.g one cell a road mao, another a text value) , you have to call different ViewControllers.
If you realy need different view contollers, then get the type you want to dispaly from the cell data
in didSelectCellRowAtIndexPath
myAppDelegate *appDelegate =
[[UIApplication sharedApplication] delegate];
UIViewController *viewControllerToDisplay;
switch (selection.type) {
case MapType:
viewControllerToDisplay = appDelegate.mapViewController;
case Picture:
viewControllerToDisplay = appDelegate.pictureViewController;
}
now push viewControllerToDisplay to navigaton Controller.
If you really need a view controller for each cell, there's no need to use a dictionary to look them up. Since selection will be by index path, an array is a better choice.
Create a custom object that has two properties: the name you want to display in the cell and a pointer to the view controller you want to push when it's selected. Load myArray with these objects instead of strings. When you are populating a cell, select the object that matches the row and use its name. When a cell is tapped, select the object that matches the row and push its controller.
(But, as others have said, if you can use the same controller and only change the data, that's the way to go!)
Here is the solution,
If you know the name of class then store all the classes name in array with dictionary having key ClassName and Xib. I prefer plist to store names but you can use other way also.
And at didselect of table or picker place the code like this,
Class classobject = NSClassFromString([[ClassArray objectAtIndex:row]valueForKey:#"ClassName"]);
id object = [[classobject alloc] initWithNibName:[[ClassArray objectAtIndex:row]valueForKey:#"Xib"] bundle:nil];
[self.navigationController pushViewController:object animated:YES];
First line will convert your string to class.
Now as we have a benefit that id can hold any object so creat the object using id. And finally you have a custom class object you can do whatever you want to do with it,here for just a demo I did navigation.
Related
I am currently working on a project that requires a list of customers to be displayed in a UITableView, the associated cell then segues to a TabView to display a detailed customer record in a tabbed ui.
I have setup the story board with the required TableView and populated fine. The TabViews all setup and I have added a custom class to the main TabView controller which can take the ID (required to interrogate service and return further data) and Customer Name. I have also added a UIViewController for the first tab in which I need to get the ID value.
I can't seem to get hold of the ID or Company Name that is passed. I have tried importing the .h file of the UITabView. I know the UITabView .h file is being populated with the values as in the .m file I am using the Customer Name to update the title of the Navigation Bar. However, whenever I breakpoint on line that gets the ID in the .m file for the individual tab, it always returns nil.
I am using the following code to try and get this value:
companyTabController *headerData = [companyTabController alloc];
_companyName_lbl.text = headerData.companyName;
_companyID_lbl.text = headerData.ID;
I have tried several variations of the above and all to no avail.
You can also use NSUserDefaults to save the data, I think that is the simplest way to save the data throughout the app.
From the code you posted, the headerData is a new instance. So the companyName and the ID will be nil unless you assign some value to them.
Since, you mentioned that you are able update the navigation bar title, try using the same object for fetching the values in this controller as well. (Maybe you can use a singleton object)
If your segueing you have to use the prepareForSegue:sender: method as such:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
companyTabController *companyTC = [segue destinationViewController];
companyTC.companyName_lbl.text = headerData.companyName;
etc
}
if your not segueing you will have to instantiate it as such :
- (void) didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *selectedCell = [self.tableView cellForRowAtIndexPath:indexPath];
companyTabController *companyTC = [self.storyboard instantiateViewControllerWithIdentifier:#"CopmanyTabController"];
companyTC.companyName_lbl.text = selectedCell.textLabel.text or = headerData.companyName;
[self.navigationController pushViewController:companyTC animated:YES];
}
My application has a Facebook login module. This will store all the users list in sqlite database and show the list of all user's name in other TableViewController.
I wants that when I click on any cell of user's name then the curresponding records show on other ViewController.
I used a property to fetch the record from TableViewController to ViewController and use push segue in it. So I have fetched the username and his email-ID. Now i want to show it in labels. How can I show it in labels.
When I used NSLog("user details %#", self.propertyName);
I got user details == {email = "sunil1234523#gmail.com"; name = "Sunil";}
However, it's not shown on labels.
How can I show username and email ID in different labels.
Can I store it in the NSDictionary and then show it. How can I store these in NSDictionary?
take an property variable NSDictionary in 2nd viewcontroller and use it like this
In 1st viewcontroller add this code, whenever u want to push the data to second viewcontroller
secondVCObj.propertyDict = userDetailsDict; //since userDetailsDict is a dictionary.
[self.navigationController pushViewController:secondVCObj animated:YES];
the dictionary is moved to second viewcontrollers property dictionary and there u can set the text to the labels like this
userNameLabel.text = [userDetailDict valueForKey:#"name"];
emailLabel.text = [userDetailDict valueForKey:#"email"];
thats it. Happy coding
i'm doing some testing of Core Data, let's say i have a mainViewController with a navigationBar and addButton.
Clicking on the addButton will open a detailViewController. When i press save to insert a new Object the detailVieController will close and show the table with the new data inserted.
I can think two different way to do that.
FIRST METHOD - Passing the ManagedObjectContext
In the action of the add button i create an instance of the new detailViewController and i pass the managedObjectContext to it. So will be the save button of the detailViewController that will take care of saving the context and then pop the controller.
This is the method called by the addButton in the MainViewController
-(void)addNewObject{
DetailViewController *detVC = [DetailViewController alloc]initWhit:self.managedObjectCOntext];
[self.navigationcontroller pushViewController:detVC animated:YES];
}
This method is called by the save button in the IngredientViewController
-(void)saveObject{
NSError *error;
if (![self.managedObjectContext save:&error]){
NSLog(#"Error");
}
}
SECOND METHOD - Using a delegate
In the action of addButton i create an instance of DetailViewController, i set it as delegate, so when i press the save button in the DetailViewCOntroller will call the delegate that will pass data to the main controller.
This is the method called by the addButton in the MainViewController
(void)addNewObject{
DetailViewController *detVC = [DetailViewController alloc]init];
detVC.delegate = self;
[self.navigationcontroller pushViewController:detVC animated:YES];
}
This method is called by the save button in the IngredientViewController
-(void)saveObject{
[self.delegate detailVCdidSaveObject];
}
This is the delegate implemented in the mainViewController
detailVCdidSaveObject{
NSError *error;
if (![self.managedObjectContext save:&error]){
NSLog(#"Error");
}
}
------------------------------ Passing the object
Is it best to pass raw data to the DetailViewController and create there the object or it's best to pass the instance of the object to DetailViewController that will take care of settin its data?
For Example
This way i link the object instance of the mainVC to the one DetailVC so i can easilly set its value
-(void)addObject{
DetailViewController *detailVC =[[DetailViewController alloc]init];
detailVC.delegate = self;
self.object = [NSEntityDescription insertNewObjectForEntityForName:#"Object" inManagedObjectContext:self.managedObjectContext];
detailVC.object = self.object;
[self.navigationController pushViewController:detailVC animated:YES];
}
this way i pass raw data and let the detailVC create the instance
-(void)addObject{
DetailViewController *detailVC =[[DetailViewController alloc]initWithName:#"objname"];
[self.navigationController pushViewController:detailVC animated:YES];
}
those code are just pseudocode for educational purpose. all ways works, i just want to know which do you think it's the most correct and why. thanks
I have used the first two methods and in my opinion they are both equally valid (though I personally prefer delegation). However, the third method caused problems if you give the user the option to cancel or go back in a navigation controller. If that happens, you will have an object that you never needed to create.
This sounds like a perfect use case for a NSFetchedResultsController. A NSFetchedResultsController is an object makes displaying data from core data in a UITableView a lot easier. It even tells you when the objects in core data matching a predicate change (insert, delete, update, move).
So the way I would do it is that MainViewController would have a NSFetchedResultsController that provides the data to the UITableView. When you press the add button, it would do what you have in the first method. The DetailViewController will create the new instance, set the values on it then save the managedObjectContext.
Since the MainViewController has the NSFetchedResultsController, it will automatically know that a new object have been created and it can update the UITableView to show it.
The NSFetchedResutsController documentation and the NSFetchedResutsControllerDelegate documentation show you exactly how to use it with a UITableView including code you can copy into your view controller that do the majority of the work.
The actual answer depends on your preference. In my project, I have implemented the first two methods. A definite No for the third method from my side because of same reasons as Kevin mentioned. If the user cancels the operation or some error occurs, then you will have to take care of removing the change (Perhaps write the following code in your didMoveToParentViewController method and cancel method):-
[self.managedObjectContext rollback]
Assuming of course that you do not have any other process modifying that managedObjectContext at the same time.
Now, I prefer the first two methods because :-
The first method allows me to write additional code in saveObject method. Lets say that you want to validate some properties before saving the object. These properties are only present in detailViewController. So, you cannot use a delegate in that situation without explicitly passing each and every property back to delegate function (which can get messy).
Now, assume that you are creating a object in your mainViewController and the detailViewController is only used to populate a field of the object that was created in mainViewController. In such a situation, I would use the delegate method and pass the field back to the mainViewController so that when the user saves the object in mainViewController, then the field values are saved along with it. If the user cancels mainViewController, then the field values are also not saved.
I have a UIViewController class and a UITableViewController class. Within the UIViewController class I have an NSMutableArray.
I now have the issue of how to load data into my table view, a separate class, I must access the NSMutableArray I used to populate the previous UIViewController class.
I tried using a delegate to access the array in the UIViewControllerClass however the array had "0 objects" and was NULL
I would appreciate some guidance in the right direction here.
You could have one view controller hold a reference to the other view controller and query the public NSMutableArray on it for data. Aaron suggested this and it might be your best solution.
Or.. you have multiple view controllers trying to access the same set of data. Potentially you have other classes which will want to access this data also. You might want to consider pulling the data out of the view controller and storying it in a neutral location. You could store it in the AppDelegate and then reference the app delegates from any place you need it.
id<UIApplicationDelegate> appDelegate = [UIApplication sharedApplication].delegate;
NSMutableArray *myData = appDelegate.data;
You could also consider pulling all the logic of your data and the data itself into a separate class and use a Singleton It would allow you to access/manipulate the data fairly easy from anywhere.
The last 2 methods would insulate data from user interface controller objects and prevent the need from potentially unrelated objects needing to hold references to one another. Used properly it will reduce code complexity and mage future changes easier to manage.
Create an NSMutableArray property on your UITableViewController class like so:
#interface CustomTableViewController : UITableViewController
#property (strong, nonatomic) NSMutableArray *dataFromOtherClass;
#end
And then when you transition, perhaps like this, you can set the dataFromOtherClass property:
CustomTableViewController *controller = [[CustomTableViewController alloc] initWithNibName:#"CustomTableViewController" bundle:nil];
controller.dataFromOtherClass = myNSMutableArrayData; // <-- Set data like this
[self.navigationController controller animated:YES];
// Or ...
[self presentViewController:controller animated:YES];
// Etc...
I currently have an array of buttons being generated which works great. Outside of this on touch up inside each button calls on a -(void)onTouch function where some math is done to determine an action. This all works great except I would like to store a history of the pressed buttons. I've tried many different ways to create an NSMutableArray and store the values of the pressed buttons, but because I can only declare the array within the -onTouch action, every time a button is pressed the array is reset so it never remembers more than one move. If I try to declare the array in my header and synthesize it outside I either get the error that nsmutable array is not a compile time thinger or it doesn't store anything (log output is "(null)". Can someone paste in some code on how to declare an array that can store and append the uibutton tags outside of where the uibutton press event happens? I'll post code later tonight if this isn't clear.
Cheers
You need to not only declare the array, but also initialise it. If you don't initialise, you won't necessarily get a warning, but you will get lots of nil data.
You only want to initialise the array once (as you have noticed) so viewDidLoad is a good place to do it. Another good place is in a custom accessor...
- (NSMutableArray*)historyArray
{
if (!_historyArray) {
_historyArray = [[NSMutableArray alloc] init];
}
return _historyArray;
}
Now the first time you try to [self.historyArray addObject:sender], the accessor will note the absence of a historyArray, create one and return it. Next time round it won't be recreated as it already exits.
#property(nonatomic,retain)NSMutableArray *tapCollection;
-(void)viewDidLoad{
self.tapCollection = [[NSMutableArray alloc] init];
}
-(void)onTouch:(id)sender{
UIButton *btnTapped = (UIButton *)sender;
[self.tapCollection addObject:[NSNumber numberWithInt:btn.tag]];
}