I am new to iphone development with objective C and XCode 4.2. Right now I successfully instantiated a UITableViewController and it can traverse a multi level json data tree. however, I'm having trouble establish the UI for the leaf page. Let me show you a snippet of code so far:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView deselectRowAtIndexPath:indexPath animated:YES];
int row = [indexPath row];
NSString * currentrow = [NSString stringWithFormat:#"%d.",row];
NSDictionary * item = [dataArray objectForKey:currentrow];
NSDictionary * children = [item objectForKey:#"children"];
if(children != nil)
{
uinavTableViewController *childView = [[uinavTableViewController alloc] init];
childView.dataArray = children;
[self.navigationController pushViewController:childView animated:YES];
}
else
{
uinavMenuItem * childView = [[uinavMenuItem alloc] init];
//uinavSecondViewController * childView = [[uinavSecondViewController alloc] init];
[self.navigationController pushViewController:childView animated:YES];
}
}
So the first part of the if statement will check if there are children nodes, if so create another uinavTableViewController. This part works perfectly.
The else statement creates a leaf page using the uinavMenuItem. When I run this in the simulator, the leaf page is just a black screen. I looked in my NSLog and can confirm that the uinavMenuItem viewDidLoad did get fired. So the controller code is working fine, but i just don't know how to link up to the view I've drawn in the storyboard.
What am I doing wrong that prevents the uinavMenuItem from displaying what I've drawn in the storyboarD?
When you want to init a view controller you made in the storyboard, use UIStoryboard's -instantiateViewControllerWithIdentifier. You define this identifier in the storyboard under the view controller's properties. Your view is loaded but empty because it's not loading what you have in the storyboard. It's probably calling -loadView because it cannot find one.
Here's where you enter the identifier:
If you are using storyboard, it is easier to just create a segue from the UITableViewController to the leaf controller. You can do this by control-clicking the table view controller and then dragging the push segue to the leaf controller. Within the X-Code sidebar, give the segue and identifier.
Then in your code, instead of calling [self.navigationController pushViewController] you can use:
if(children != nil)
{
[self performSegueWithIdentifier: #"YourIdentifierHere" sender: self];
}
else
{
[self [performSegueWithIdentifier: #"TheOtherIdentifier" sender: self];
}
To pass data to the uinavTableViewController, you use the prepareForSegue method in the same controller that performs the segue:
- (void)prepareForSegue: (UIStoryboardSegue *)segue sender: (id)sender
{
// Because you have two segues, you need to check the identifier
if ([[segue identifier] isEqualToString: #"YourIdentifierHere"])
{
uinavTableViewController childView = (uinavTableViewController *)segue.destinationViewController;
childView.dataArray = children;
}
}
Related
How to pass values from one controller to another??? I use a StoryBoard.
I would like this to appear on the highlighted text view of the first view.
Call the next view of the code, I think something like this should look like:
UIStoryboard *finish = [UIStoryboard storyboardWithName:#"MainStoryboard" bundle:nil];
UIViewController *viewController = [finish instantiateViewControllerWithIdentifier:#"FinishController"];
viewController.modalPresentationStyle = UIModalPresentationPageSheet;
[self presentModalViewController:viewController animated:YES];
finishcontroller:
- (void)viewDidLoad
{
self.lblFinishTitle.text=self.FinishTitle;
self.lblFinishDesc.text = self.FinishDesc;
self.lblFinishPoint.text=self.FinishPoint;
[super viewDidLoad];
// Do any additional setup after loading the view.
}
first view:
-(void) prepareForSegue:(UIStoryboardPopoverSegue *)segue sender:(id)sender
{
if ([segue.identifier hasPrefix:#"FinishController"]) {
FinishController *asker = (FinishController *) segue.destinationViewController;
asker.FinishDesc = #"What do you want your label to say?";
asker.FinishTitle = #"Label text";
asker.FinishPoint = #"asdas";
}
}
I want to pass a value causing the transmission of the code
The issue is that you aren't actually using that segue, you are using presentModalController instead.
Note that usually, you can just ask self for it's storyboard. However, even that is unnecessary when you have a segues connected:
[self preformSegueWithIdentifier:#"FinishController" sender:self];
Then prepareForSegue will be called. Also note that you can (should) use something more authoritative than the segue identifier to determine if you should load the data... you can ask the segue's destination controller if it is the right class:
-(void) prepareForSegue:(UIStoryboardPopoverSegue *)segue sender:(id)sender
{
if ([segue.destinationViewController isKindOfClass:[FinishController class]]) {
FinishController *asker = (FinishController *) segue.destinationViewController;
asker.FinishDesc = #"What do you want your label to say?";
asker.FinishTitle = #"Label text";
asker.FinishPoint = #"asdas";
}
}
You are probably already aware (since you used the identifier in your code), but for the benefit of future discoverers of this post; segues are given identifiers in the inspector panel of Xcode when you are in the storyboard.
I'm building an iPhone app, with the following structure:
I have the MainViewController which consists of 2 views (like split screen).
The first view of them, has a button. On tap, a UItableView (ResultTableViewController) appears in the second (of the above) view:
-(IBAction)buttonTapped:(id)sender {
if ([(UIButton *)sender tag] == 0) {
ResultsTableViewController *childViewController = [[ResultsTableViewController alloc] init];
childViewController.tableView.delegate = self.results;
[self.results.view addSubview:childViewController.tableView];
}
}
So I have a UItableView as a sub-view of a UIView.
The problem is that pushViewController() in didSelectRowAtIndexPath() of ResultTableViewController does not work (self.navigationController is nil).
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
DetailsViewController *detailView = [[DetailsViewController alloc] initWithNibName:#"DetailsViewController" bundle:[NSBundle mainBundle]];
[self.navigationController pushViewController:self.detailView animated:YES];
}
I have tried many of the solutions I found, but nothing works.
In my MainWindow.xib, I have only MainViewController added, is that the problem?
Thanks in advance!
You are adding the view of the child controller to your controller's view, not pushing the child controller onto your navigation stack. Due to that, your child controller's navigation controller will be nil, since it wasn't put into the navigation controller.
Is this what you're going for?
-(IBAction)buttonTapped:(id)sender {
if ([(UIButton *)sender tag] == 0) {
ResultsTableViewController *childViewController = [[ResultsTableViewController alloc] init];
childViewController.tableView.delegate = self.results;
[self.navigationController pushViewController:childViewController animated:YES];
}
}
[self.navigationController pushViewController:self.detailView animated:YES];
above line of code will push the detailview on navigationcontroller stack. Check whethere your tableviewcontroller is on that stack ?
Ok, I found it.
I had to declare my MainViewController as UINavigationControllerDelegate and create a secondary NavigationController in it. I push the viewController in my new navigationController and that's it.
In the app that io am creating, i have a custom copy of UISplitView Controller, MGSplitViewController. I have implemented it into my project which started off with the MultipleDetailViews sample code from apple.
I have come across a problem where i cant seem to switch between viewcontrollers. When i push the tableview cells, the detailview controller should change according the the nib assigned, however that doesn't happen.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
/*
Create and configure a new detail view controller appropriate for the selection.
*/
NSUInteger row = indexPath.row;
UIViewController *detailViewController = nil;
if (row == 0) {
FirstDetailViewController *newDetailViewController = [[FirstDetailViewController alloc] initWithNibName:#"FirstDetailView" bundle:nil];
detailViewController = newDetailViewController;
}
if (row == 1) {
SecondDetailViewController *newDetailViewController = [[SecondDetailViewController alloc] initWithNibName:#"SecondDetailView" bundle:nil];
detailViewController = newDetailViewController;
}
// Update the split view controller's view controllers array.
NSArray *viewControllers = [[NSArray alloc] initWithObjects:self.navigationController, detailViewController, nil];
splitViewController.viewControllers = viewControllers;
[viewControllers release];
[detailViewController release];
Normally this code would be enough to change the views in the original Multiple Detail View code.
has anyone run into a similar problem? any ideas?
You're just creating new view controllers. You're not adding them anywhere. You add view controllers to the split view controller by using its viewControllers property.
EDIT: I've used MGSplitViewController, but I never tried to change the detail view like that. I just pushed the new detail view controller onto the navigation controller. Is there a specific reason for wanting to change the detail view controller entirely?
Basically when I implement a split view like the one presented in the Apple example 'MultipleDetailsViews', everything works fine, it allocates a new detailed view each time a row is selected. Here is the relevant code from the example:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
UIViewController <SubstitutableDetailViewController> *detailViewController = nil;
if (row == 0) {
FirstDetailViewController *newDetailViewController = [[FirstDetailViewController alloc] initWithNibName:#"FirstDetailView" bundle:nil];
detailViewController = newDetailViewController;
}
// ...
NSArray *viewControllers = [[NSArray alloc] initWithObjects:self.navigationController, detailViewController, nil];
splitViewController.viewControllers = viewControllers;
// ...
}
But what I'm looking for is reusing detailed view controllers, that is lazily allocating a view controller when it is selected, and keeping a reference on it in my object. That way when another row is selected the view controller is not deallocated and when it is selected again it would be reused instead of allocating a new one. Here is the relevant code:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
UIViewController <SubstitutableDetailViewController> *detailViewController = nil;
if (row == 0) {
if (self.firstDetailViewController == nil) {
FirstDetailViewController *newDetailViewController = [[FirstDetailViewController alloc] initWithNibName:#"FirstDetailView" bundle:nil];
self.firstDetailViewController = newDetailViewController;
[newDetailViewController release];
}
detailViewController = self.firstDetailViewController;
}
// ...
NSArray *viewControllers = [[NSArray alloc] initWithObjects:self.navigationController, detailViewController, nil];
splitViewController.viewControllers = viewControllers;
// ...
}
self.firstDetailViewController is instanciated the first time the first row is selected then it is reused.
When I'm doing that it works well in landscape mode but in portrait mode after a few clicks in the popover menu it raises an exception: *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Popovers cannot be presented from a view which does not have a window.'
So why I care? and why I don't want to reallocate view controllers? because in some of them I want to perform tasks that wouldn't be interrupted (killed) if the user navigate in a new detailed view while the task isn't completed yet.
Is there someone with an idea of what happens or with a working implementation of what I'm trying to achieve?
The view controllers are designed to be created and thrown away, if you need something to run in the background for longer then best move it into the master view controller or a separate object.
If you do however want to experiment with reusing a view controller this can be achieved by setting the viewControllers property on the new navigation controller with the previous detail controller saved in the viewDidLoad:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([[segue identifier] isEqualToString:#"showDetail"]) {
NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
NSManagedObject *object = [[self fetchedResultsController] objectAtIndexPath:indexPath];
UINavigationController* navigationController = (UINavigationController*)[segue destinationViewController];
// reuse existing controller
navigationController.viewControllers = #[self.detailViewController];
// update the detail controller as normal.
[controller setDetailItem:object];
controller.navigationItem.leftBarButtonItem = self.splitViewController.displayModeButtonItem;
controller.navigationItem.leftItemsSupplementBackButton = YES;
}
}
I'm working on a navigation-based application.
My rootViewController contains 3 cells
-When the first one is pressed a UIViewController is pushed (THIS WORKS)
-The problem is with the 2nd and 3rd cells that are supposed to push a UITableViewController
The app is running with no errors and it is NOT crashing at all, but when i navigate to the tableview, an empty table is viewed with no elements in it.
Is the problem with this part of the code?? :
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
UIViewController *detailViewController = [[UIViewController alloc] initWithNibName:#"Introduction" bundle:nil];
detailViewController.title=#"Introduction";
UITableViewController *myPictures = [[UITableViewController alloc] initWithNibName:#"Pictures" bundle:nil];
myPictures.title=#"Pictures";
UITableViewController *myImages = [[UITableViewController alloc] initWithNibName:#"ImagesViewController" bundle:nil];
myImages.title=#"Images";
// Pass the selected object to the new view controller.
if (0 == indexPath.row)
[self.navigationController pushViewController:detailViewController animated:YES];
if (1== indexPath.row)
[self.navigationController pushViewController:myPictures animated:YES];
if (2== indexPath.row)
[self.navigationController pushViewController:myImages animated:YES];
[detailViewController release];
[myPictures release];
[myImages release];
What you're doing it very wrong (apart from your original problem). Why are you instantiating each view controller and then only using the one based on the current 'cell selection'? This will slow your app down depending on how much time it would take to load each of these separate views. You should only instantiate the relevant view controller inside the "if (indexPath.row == 2) {" block.
Apart from that there are many things wrong about your approach. What you are doing will never work since instantiating a generic UITableViewController (even if you supply your own nib) will obviously only show you an empty view. You need to have a class of your own which the nib is tied to as a delegate which will then supply the TableView with data.
I believe when you created these nib files (for example "Pictures") xcode also give you a 'PicturesViewController.h and PicturesViewController.m" files? If so, you need to write the appropriate code there and ensure the Tableview in the "Pictures" nib file has its 'datasource' and 'delegate' set to 'PicturesViewController'. Then when you want to show that view do this instead:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.row == 1) {
...
} else if (indexPath.row == 2) {
PicturesViewController *myPictures = [[PicturesViewController alloc] initWithNibName:#"Pictures" bundle:nil];
[self.navigationController pushViewController:myPictures animated:YES];
[myPictures release];
}
}