How to programmatically create and populate nested UITableViewControllers? - ios

I have an application that starts with a list of tasks and drills down through one or two list views to a final UIViewController. Due to the project requirements, these view controllers need to be created and navigated programmatically, not via segues in Interface Builder. I have accomplished this with single lists and regular view controllers, but one case has me stumped.
I currently have several UITableViewControllers in Interface Builder that point to other UITableViewControllers, basically a drill-down list. I'm unsure how to create a list item in my UITableViewController that creates another UITableViewController populated with list items I send it, and then navigates to it.
I need the UITableViewController to be generic, so I can create multiple drill-down lists using the same UITableViewController code.
Does this make sense? I feel like it should be a simple thing, but my brain just hasn't made the connection on how yet.
EDIT: Let me try to better illustrate my problem. My UITableViewController contains list items that are passed to it, and an array of objects associated with those items. The UITableViewController uses these objects to create the final UIViewController screen in most cases.
TableVC.h
#interface TableVC : UITableViewController <UITableViewDataSource, UITableViewDelegate>
//Menu items
#property NSMutableArray *itemList;
//Objects to pass to the detail ViewController
#property NSMutableArray *itemObjectList;
- (void)initWithList:(NSMutableArray *)items :(NSMutableArray *)objects :(NSString *)title;
#end
TableVC.m
NSMutableArray *items = [NSMutableArray arrayWithObjects:
NSLocalizedString(#"List Item 1",nil),
NSLocalizedString(#"List Item 2",nil), nil];
NSMutableArray *objects = [[NSMutableArray alloc]init];
MenuListObject *obj1 = [[MenuListObject alloc]init];
obj1.name =NSLocalizedString(#"Name",nil);
obj1.department=NSLocalizedString(#"Department",nil);
obj1.address=#"Address";
obj1.imgName=#"XXXYY.jpg";
obj1.menuIndex=0;
obj1.typeFlag=#"F";
...
[objects addObject:obj1];
[objects addObject:obj2];
TableVC *vc = [[TableVC alloc]init];
[vc initWithList:items :objects :NSLocalizedString(#"My Title",nil)];
[self.navigationController pushViewController:vc animated:YES];
However in some cases the list items don't go to a UIViewController. Instead they lead to another list, which then ends up at the UIViewController. I know how to create list items, but not a list item that leads to another UITableViewcontroller.
If there's a better way to handle all of this, I'm open to suggestions.

You can do this with segues aswell. Just make a "dummy segue" from a bar button item back to the view controller itself.
See this screenshot:
Then you can trigger this segue programmatically - i.e. when tapping a cell in a table view - to make an "infinite" stack of the same view controller.
And if you don't want segues. Just create the view controller programmatically, and push it manually. The same "reuse"-concept applies here.

Declare a class of type UITableViewController.
In it's .h file, declare an NSArray to hold the data.
In the .m file, implement the tableview delegate and data source methods, using the array as the data source.
In your calling tableViewController, in tableView:didSelectRowAtIndexPath:,
create a new instance of the tableviewController class,
then set it's array to the list of items you want to pass it,
and push it:
[self.navigationController pushViewController:_the_new_instance_ animated:YES];

I ended up using a third type flag in my TableViewController to denote when a list item should fire off a new list view rather than go to my destination UIViewController.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
MenuListObject *obj = nil;
for (int i=0; i<self.itemObjectList.count; i++){
obj = [self.itemObjectList objectAtIndex:i];
if (obj.menuIndex == indexPath.row){
break;
}
}
if (obj!=nil)
{
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:[NSBundle mainBundle]];
if ([obj.typeFlag isEqualToString:#"F"])
{
if (obj.url ==nil)
{
FacilityDetailVC *vc = [storyboard instantiateViewControllerWithIdentifier:#"FacilityDetail"];
vc.name=obj.name;
vc.department=obj.department;
...
[self.navigationController pushViewController:vc animated:YES];
}
else if ([obj.typeFlag isEqualToString:#"T"])
{
TextDetailVC *vc = [storyboard instantiateViewControllerWithIdentifier:#"TextDetail"];
vc.titleText=obj.titleText;
vc.descriptionText=obj.descriptionText;
vc.URL=obj.url;
[self.navigationController pushViewController:vc animated:YES];
}
//This is the part I added, to redirect to the UITableViewController
else if ([obj.typeFlag isEqualToString:#"L"])
{
TableVC *vc = [[TableVC alloc]init];
[vc initWithList:obj.listItems :obj.listObjects :obj.titleText];
[self.navigationController pushViewController:vc animated:YES];
}
}

Related

Correct way of doing it? Reusing same storyboard view and view controller

I am doing like below. The idea is to pass different values and display data based on type supplied from the menu button.
But it is not working as it should.
It is just displaying which ever view is called first (or data is not refreshed on second button press shows the first view).
So what is the correct way of doing it?
if (indexPath.row == 1) {
SomeViewController* v = [self.storyboard instantiateViewControllerWithIdentifier:#"storyboardCat"];
v.type=#"1";
[self showViewController:v];
[self.slidingViewController resetTopViewAnimated:YES];
}
if (indexPath.row == 2) {
SomeViewController* v = [self.storyboard instantiateViewControllerWithIdentifier:#"storyboardCat"];
v.type=#"2";
[self showViewController:v];
[self.slidingViewController resetTopViewAnimated:YES];
}
Edit -for more information
SomeViewController.h file
#interface SomeViewController : UIViewController
#property (nonatomic, strong) NSString *type;
#end
SomeViewController.m file
#implementation SomeViewController
#synthesize type;
arrayData = [NSMutableArray arrayWithArray:[CoreDataController getSomeData:type]];
//so trying to populate different values in tableview based on passed parameter to function
But now which ever view is called first or value is passed (either 1 or 2), only those values is populated and values does not change on simultaneous call passing another value(for e.g. when index row is 2).
Try this
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle: nil]; //Change Main if your storyboard name is different.
SomeViewController* v = [storyboard instantiateViewControllerWithIdentifier:#"launchView"]; // launchView is the name given to the view controller in the properties as shown in below screenshot
Hope this helps

Passing array to ViewController, loading ViewController, then refreshing UITableView

This is my first time posting and I was just having issues with a "get your feet wet" project (simple tip calculator) I am doing in my spare time.
What I want to do is, to pass an array from ViewController1 to ViewController2, then I want to load ViewController2 and refresh UITableView that is located on ViewController2.
I use ViewController1 to take the user submitted data and calculate the results. I want to take those results and send it over to ViewController2 to display the results. I'm doing this because it's a tabbed application and I want the results to display on another tab.
Here is how I pass the data from ViewController1 to ViewController2
-(void) displayCalculationsView:(NSMutableArray *)array{
UIStoryboard *story = [UIStoryboard storyboardWithName:#"Main_iPhone" bundle:nil];
CalculationsViewController *viewController2 = [story instantiateViewControllerWithIdentifier:#"ViewController2" ];
[viewController2 setArrayValueAndRefreshTable:array];
[self.tabBarController setSelectedIndex:1];
}
Here is what is on the receiving end:
-(void)setArrayValueAndRefreshTable:(NSMutableArray *)array{
if (calculationsDisplayArray == nil) {
calculationsDisplayArray = [[NSMutableArray alloc] init];
}
[calculationsDisplayArray removeAllObjects];
[calculationsDisplayArray addObjectsFromArray:array];
for (NSString *data in pushedDisplayArray) {
NSLog(#"PUSHED ARRAY DATA: %#", data);
}
[tableView reloadData];
}
The data seems to be received fine because it displays all the correct data in the NSLog. However, when the [self.tabBarController setSelectedIndex:1]; part hits, it wipes out my calculationsDisplayArray, after the page has loaded.
Is there any way to prevent it from becoming nil? I'm using ARC and it doesn't let me use [arrayname retain]; I'm not sure I want to use segue because I don't have access to my array in the segue method (or do I?), so I would not be able to send it over to the other view.
Once again, I am new at this. So if there is any more clarification needed, please tell me and I will update this post. Thank you.
As others have pointed out, you have created a new view controller instead of accessing the one in your tab bar. You should access your existing view controller:
CalculationsViewController * viewController2 = [self.tabBarController.viewControllers objectAtIndex:1];

Manage few UIViewControllers in parent view controller

I have UIViewController that contain another two UIViewControllers as properties.
MenuViewController contains:
#property (nonatomic, strong) TeamsViewController *teamsViewController;
#property (nonatomic, strong) ResultsViewController *resultsViewController;
The MenuViewController contains table view and when user tap on the cell "show teams" I need to initialize teamsViewController and show teamsViewController view. The same thing when user press on the cell "show results" but in this case I need to show resultsViewController view.
So, I usually do this is in one way initialize controllers when cell is pressed and call addSubview method that will add controllers view. But I think it is not good solution am I right?
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (_teamsViewController) _teamsViewController = nil;
_teamsViewController = [TeamsViewController new]
[self.view addSubView:_teamsViewController];
}
Is the method above ok?
There is my hierarchy of view each of them managed by its own controller. So the white you managed by MenuViewController and the gray view managed by ResultViewController and blue view managed by TeamsViewController.
As I said before when I tap on appropriate cell in menu view controller I need to show teams or results. But each of this view has another view controller. Or maybe I confused about view controller paradigm? Maybe TeamsViewController should be a TeamsView and ResultsViewController should be ResultsView ? So both view controllers has the table as well that managed in their controllers. So i don't think it has to be a UIView instead of UIViewController.
I think your best bet is to set this up as a UINavigationController. UINavigationController inherits from UIViewController so you aren't losing any functionality this way. You could then set it up like this:
//MenuViewController.h
#interface MenuViewController : UINavigationController
#property (nonatomic, strong) TeamsViewController *teamsViewController;
#property (nonatomic, strong) ResultsViewController *resultsViewController;
//Insert other properties and methods....
#end
and in the method called when someone clicks on a cell, you would simply do this:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (!self.teamsViewController) {
self.teamsViewController = [[UIViewController alloc] initWithNibName:#"nibName" bundle:nil];
}
[self pushViewController:self.teamsViewController animated:YES];
}
Now, you have two view controllers, so you have to tell your method above which one to push onto the stack. If the rows are fixed, you can simply decide which one to show based on indexPath but if it's more dynamic (i.e. tableview is created from a database) then you'll need to have some more complex logic code here.
Without making too many assumptions, but to give you some general guidance, when you create a cell you would generally set some sort of flag on it to indicate what type it is. This can be done with an NS_ENUM or with a simple BOOL if you only have two states (I prefer to use ENUMs whenever possible as they are much more descriptive). You would then check for the existence of that flag in the tableView:didSelectRowAtIndexPath: method, pushing the corresponding view onto the navigation stack. Something like this (this is not literal code, but shown just to give you an idea:
// This code assumes in the method tableView:cellForRowAtIndexPath:
// you have set the tag property to '1' if a TeamsViewController is
// needed or '2' if a ResultsViewController is needed when that cell
// is pressed.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
self.teamsViewController = self.teamsViewController ? self.teamsViewController : [[UIViewController alloc] initWithNibName:#"TeamsViewController" bundle:nil];
self.resultsViewController = self.resultsViewController ? self.resultsViewController : [[UIViewController alloc] initWithNibName:#"ResultsViewController" bundle:nil];
switch ([[tableView cellForRowAtIndexPath:indexPath] tag]) {
case 1:
[self pushViewController:self.teamsViewController animated:YES];
break;
case 2:
[self pushViewController:self.resultsViewController animated:YES];
break;
default:
break;
}
}
You would still need to do your initialization in your teams or results view controller to show the view but this should steer you in the general direction.
I'd recommend using Storyboards and making sure to have a "Storyboard ID" for each of them. That way, it becomes a bit easier to push various UIViewController instances, as needed. This is my typical pattern:
UIViewController *vc = [self.storyboard
instantiateViewControllerWithIdentifier:#"selected identifier"];
[self.navigationController pushViewController:vc animated:YES];
Also, unless you need to set properties in the child view controller, there's no need to have a property reference to it. Further, this answer assumes that you're using a UINavigationController, with your MenuViewController set as the rootViewController. You can either set this up in IB with a Storyboard (easy and probably the preferred way), or in code like this:
MenuViewController *vc = [[UIViewController alloc] initWithNibName:#""
bundle:nil];
UINavigationController *navVc = [[UINavigationController alloc]
initWithRootViewController:vc];

How can I show a new view from the UITableViewController contained my UIViewController?

I have a UITableViewController within a UIViewController. While this table viewcontroller was the only one involved, it was pushing views just fine when the user would tap a row. However, ever since I moved it to be one of two contained within the UIViewController, the taps of rows suddenly do nothing.
I've tried searching around and I'm not the first to run into this problem, but none of the answers fit my circumstances or the questions have no working answers. That link was the closest I found, but I'm not using storyboards -- I'm using separate XIBs.
So how do I push a new view from a viewcontroller within a viewcontroller?
To recap:
Here is what I had, and it worked fine in taking users to a new screen!
// Normal table behavior, as illustrated by [another question][2].
- (void)tableView:(UITableView *)tableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
SomeView *detailViewController = [[SomeView alloc] initWithNibName:#"SomeView" bundle:nil];
// Pass the selected object to the new view controller.
[self.navigationController pushViewController:detailViewController animated:YES];
}
Now I have the viewcontroller as a property in a view -- and the above code, which is in the file for the tableviewcontroller and not at the "main" view, doesn't cause a new screen to appear anymore!
Thanks for the comments! Here's some code to clarify my scenario.
The controllers within a controller. This is a file from a test project I've been using to test the concept out. In this case, I have a tableview controller within a tableview controller.
#interface SimpleTableViewController : UIViewController <UITableViewDelegate, UITableViewDataSource>
// This is the controller within the controller
#property IBOutlet SecondTableViewController *secondTableController;
#property IBOutlet UITableView *secondTable;
My SecondTableViewController has this fun bit.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// Navigation logic may go here. Create and push another view controller.
UIViewController *detailViewController = [[UIViewController alloc] initWithNibName:#"SimpleNonTableViewController" bundle:nil];
// ...
// Pass the selected object to the new view controller.
[manualViewControllerParent.navigationController pushViewController:detailViewController animated:YES];
}
The view that the user interacts with is hooked up to SimpleTableViewController. In this way, SecondTableViewController is "within" SimpleTableViewController. Feel free to comment if you'd like more details!
I've put my test/concept project on github. https://github.com/hyliandanny/TableViewCeption
You need to use a custom container controller to do what you want. It would be easiest if you used a storyboard, but you can do it in code with xibs as well. The outer controller should be a UIViewController, not a table view controller. You can do something like this (in the outer controller):
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
UIViewController *detailViewController = [[UIViewController alloc] initWithNibName:#"SimpleNonTableViewController" bundle:nil];
[self addChildViewController:detailViewController];
detailViewController.view.frame = set the frame to what you want;
[self.view addSubview:detailViewController.view];
[detailViewController didMoveToParentViewController:self];
}
You should read up on Apple's documentation for custom container controllers.
What you need to make sure:
Your UITableView delegate is hooked up to your controller. Otherwise it wouldn't call didSelectRow. You can do this in xib or in viewDidLoad method.
Your self.navigationController is not nil
Your detailViewController is not nil
I also think that what you mean is you have UITableView inside your UIViewController. UITableView is only the view, whereas UITableViewController is a controller. You can't have a controller inside another controller.

addchildviewcontroller pass data

I'm trying to pass data to a childviewcontroller.
I have a view controller with two buttons and one view. Pressing the buttons defines the view that is shown.
The specific case is I want to show a list of items. The first way (button) is in a list, the second on a mapview. To show the items i need to pass a category to the child.
In my viewDidLoad I add both viewcontrollers with addchildviewcontroller en set my view to the view of the listviewcontroller.
-(void)viewDidLoad
{
[super viewDidLoad];
UIStoryboard* sb = [UIStoryboard storyboardWithName:#"MainStoryboard" bundle:nil];
ItemListViewController * itemListViewController = (ItemListViewController *)[sb instantiateViewControllerWithIdentifier:#"ItemList"];
itemListViewController.view.frame = detailView.bounds;
[self addChildViewController:itemListViewController];
[itemListViewController didMoveToParentViewController:self];
itemListViewController.category = category;
ItemListMapViewController * itemListMapViewController = (ItemListMapViewController *)[sb instantiateViewControllerWithIdentifier:#"ItemListMap"];
itemListMapViewController.view.frame = detailView.bounds;
[self addChildViewController:itemListMapViewController];
itemListMapViewController.category = category;
childControllers = [NSArray arrayWithObjects:itemListViewController, itemListMapViewController, nil];
[self.detailView addSubview:itemListViewController.view];
currentPage = 0;
}
Acoording to the button pressed I change the my view
- (IBAction)buttonClicked:(id)sender
{
UIButton * button = sender;
UIViewController *source = (UIViewController *)[childControllers objectAtIndex:currentPage];
[source.view removeFromSuperview];
UIViewController *destination = (UIViewController *)[childControllers objectAtIndex:button.tag - 100];
[self.detailView addSubview:destination.view];
currentPage = button.tag - 100;
button = nil;
}
But passing the category to my childviewcontrollers does nothing. The category in my childcontrollers is always null.
I also tried to get the category by accessing the parentviewcontroller on the childviewcontroller,
NSLog(#"::%#", ((ItemListHeaderViewController *)self.parentViewController).category);
but this also results in null.
I don't know what I'm doing wrong or maybe I'm understanding the whole containment story wrong... I'm new to ios development, so don't shoot me if the question is stupid. :) This is also my first question on stack overflow, so again don't shoot if I did something wrong.
Help would be appreciated. Thanks in advance.
Kind regards...
In the child view controller .h:
#property(nonatomic, strong) NSString *category;
Child VC .m:
#synthesize category;
Then get a reference to that class in the parent class and set the property. I wonder why you are doing this in code. Laying this out in IB is soooo much easier and working with segues more straightforward to me.

Resources