In iOS 8, view controllers can now call showDetailViewController:sender: to have the system determine the proper view controller to present the detail view controller.
In my app, I have a UISplitViewController, which contains two UINavigationControllers in its viewControllers array. The first UINavigationController contains my 'master' view, a subclass of UITableViewController. The second UINavigationController contains my 'detail' view.
Since I'm trying to make this work universally, I'm trying to use showDetailViewController:sender: to display the detail view:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
self.itemVC.item = self.itemStore.items[indexPath.row];
[self showDetailViewController:self.itemVC sender:self];
}
This works fine with the Horizontal Compact trait (iPhone style), when self.splitViewController.collapsed == YES, but not when the trait is Regular (iPad, not collapsed). On the iPad, it replaces the detail UINavigationController with the bare detail view controller (instead of replacing that UINavigationController's viewControllers array).
To get around this, I'm tested for whether or not it's collapsed, and if it isn't, I'm wrapping the detail view controller in another UINavigationController before showing it:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
self.itemVC.item = self.itemStore.items[indexPath.row];
UIViewController *vcToShow;
// For whatever reason, when not collapsed, showDetailViewController replaces the detail view, doesn't push onto it.
if (self.splitViewController.collapsed) {
vcToShow = self.itemVC;
} else {
vcToShow = [[UINavigationController alloc] initWithRootViewController:self.itemVC];
}
[self showDetailViewController:vcToShow sender:self];
}
I suppose alternatively I could just configure self.itemVC and avoid calling showDetailViewController:sender: altogether when self.splitViewController.collapsed == NO:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
self.itemVC.item = self.itemStore.items[indexPath.row];
// For whatever reason, when not collapsed, showDetailViewController replaces the detail view, doesn't push onto it.
if (self.splitViewController.collapsed) {
[self showDetailViewController:vcToShow sender:self];
}
}
But, this feels like it's defeating the purpose of showDetailViewController:sender:, which is to loosen up the coupling between self and the rest of the view hierarchy.
Is there a better way to handle this?
In showDetailViewController:sender: depending on the collapse property you need to create the controller you want to show in the detail.
E.g. On the iPad in landscape mode it would already create the detail view controller from the storyboard but on the iPhone 5 where it is collapsed the view controller does not exist yet.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
UINavigationController *detail;
ImageViewController *imageVC;
// on the iPhone (compact) the split view controller is collapsed
// therefore we need to create the navigation controller and its image view controllerfirst
if (self.splitViewController.collapsed) {
detail = [[UINavigationController alloc] init];
imageVC = [self.storyboard instantiateViewControllerWithIdentifier:#"ImageViewController"];
[detail setViewControllers:#[imageVC] animated: NO];
}
// if the split view controller shows the detail view already there is no need to create the controllers
else {
id vc = self.splitViewController.viewControllers[1];
if ([vc isKindOfClass:[UINavigationController class]]) {
detail = (UINavigationController *)vc;
imageVC = [detail.viewControllers firstObject];
}
}
[self prepareImageViewController:imageVC forPhoto:self.photos[indexPath.row]];
// ask the split view controller to show the detail view
// the controller knows on iPhone and iPad how to show the detail
[self.splitViewController showDetailViewController:detail sender:self];
}
I hope this solves your issue.
The way You doing it have a problem. If your rotate the device(change the mode from collapsed to allVisible) after you select, you will find the detail vc without a navigation controller.
If you call showDetailViewController:sender: in all cases and pass the view controller with a navigation controller it will work fine in both cases and also will fix the rotaion problem mentioned above.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
self.itemVC.item = self.itemStore.items[indexPath.row];
UIViewController *vcToShow= [[UINavigationController alloc] initWithRootViewController:self.itemVC];
[self showDetailViewController:vcToShow sender:self];
}
if (self.splitViewController.collapsed)
[self.splitViewController showDetailViewController:self.itemVC sender:self];
else
self.splitViewController.preferredDisplayMode = UISplitViewControllerDisplayModePrimaryHidden;
Related
I use the SLPagingView (https://github.com/StefanLage/SLPagingView/) open source library for a paging view based app like Twitter or Tinder.
From some page I open a detail view. When I come back the layout of the paging views is broken.
DetailViewController.m
- (void)backButtonPressed {
[self dismissViewControllerAnimated:YES completion:nil];
}
In fact I noticed that the hierarchy of the app is broken just before the DetailView appears.
SomePageViewController.m
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// Perform segue to detailView
[self performSegueWithIdentifier:#"actionDetail" sender:nil];
/* Does not work with a modal view controller either
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main"bundle:nil];
DetailViewController *detailViewController =
(DetailViewController *)
[storyboard instantiateViewControllerWithIdentifier:#"detailcontroller_id"];
[self pushViewController:detailViewController animated:YES];*/
}
I got the Presenting view controllers on detached view controllers is discouragedwarning. I tried to replace self by self.navigationControlleror self.parentViewController.navigationControllerbut both don't work.
Does anyone using this library fix this issue or know a solution?
Fixed.
I modified the SLPagingView library. Add [self addChildViewController:ctr] in initWithNavBarControllers:, initWithNavBarItem: and other life cycle methods.
Check the differences: https://www.diffchecker.com/cnlvslym
UIViewController appears as black screen when handling programmtically
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *user = (NSString *) [self.friends objectAtIndex:indexPath.row];
ChatViewController *chatController = [[ChatViewController alloc] initWithUser:user];
[self presentModalViewController:chatController animated:YES];
}
This below given code is in the chatviewcontroller
- (id) initWithUser:(NSString *) userName {
if (self = [super init]) {
chatWithUser = userName;
}
return self;
}
and when i do it using storyboard segue then only tableview row gets selected but doesn't shows ChatViewController
else if ([segue.identifier isEqualToString:#"showChatView"]) {
ChatViewController *viewController = (ChatViewController *)segue.destinationViewController;
viewController.chatWithUser = friends;
}
If anyone can figure out what i m doing wrong. Will appreciate so much.
Thanks for help though.
presentModalViewController:animated: is deprecated (since iOS 6), you should use presentViewController:animated:completion:
However, it looks like you are using a segue to get to your ChatViewController, so you shouldn't even have to present the view controller since this is handled by Interface Builder. If your segue is set up correctly, replace presentModalViewController:animated: with [self performSegueWithIdentifier:#"showChatView" sender:nil];
EDIT
You should just move your ChatViewController setup to the prepareForSegue:sender: method, like so:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *user = (NSString *)[self.friends objectAtIndex:indexPath.row];
[self performSegueWithIdentifier:#"showChatView" sender:user];
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"showChatView"]) {
NSString *user = (NSString *)sender;
ChatViewController *chatVC = (ChatViewController *)[segue destinationViewController];
// No need to have an init method with the user property since Interface Builder does that for you.
chatVC.chatWithUser = user; // Expose this property in ChatViewController's header file if it's not already
}
That should be all you need to do in your code.
Presenting Versus Showing a View Controller
The UIViewController class offers two ways to display a view controller:
The showViewController:sender: and showDetailViewController:sender: methods offer the most adaptive and flexible way to display view controllers. These methods let the presenting view controller decide how best to handle the presentation. For example, a container view controller might incorporate the view controller as a child instead of presenting it modally. The default behavior presents the view controller modally.
The presentViewController:animated:completion: method always displays the view controller modally. The view controller that calls this method might not ultimately handle the presentation but the presentation is always modal. This method adapts the presentation style for horizontally compact environments.
The showViewController:sender: and showDetailViewController:sender: methods are the preferred way to initiate presentations. A view controller can call them without knowing anything about the rest of the view controller hierarchy or the current view controller’s position in that hierarchy. These methods also make it easier to reuse view controllers in different parts of your app without writing conditional code paths.
Refer this link to know about what is the difference between segue programmatically and using interface builder. I hope it is helpful.
#timgcarlson's answer is great for solving your problem.
As per #Sneha's suggestion I have added a paragraph that feel is useful.
In a split view controller app,how can I segue to different detail view controllers upon selecting a table row in the master view controller?
Just to be clear, I need the detail view controller to be replaced when I select a row in the master view controller. How do I wire up the view controllers? From the split view controller? or from the detail view navigation controller?
Implement tableView:didSelectRowAtIndexPath: in the master table view's delegate. Depending on the value of the indexPath parameter, call [detailViewController performSegueWithIdentifier:sender:] with the segue identifier of your choice.
In your tableView:didSelectRowAtIndexPath: method, do this:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[self performSegueWithIdentifier:#"YourSegueIdentifier" sender:self];
}
If you need to perform different segues based on the selected row, do this:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *segueIdentifier = nil;
switch(indexPath.row) {
case 0:
segueIdentifier = #"YourSegueIdentifier";
break;
case 1:
segueIdentifier = #"ADifferentSegueIdentifier";
break;
.
.
.
}
if (segueIdentifier != nil) {
[self performSegueWithIdentifier:segueIdentifier sender:self];
}
}
// Get detail navigation controller
UINavigationController *detailNavigationController = [splitViewController.viewControllers objectAtIndex:1];
// Push the detail view controller
[detailNavigationController pushViewController:anyDetailViewController animated:NO];
// You also might need to set the splitview controller's delegate to this view controller
splitViewController.delegate = anyDetailViewController;
Use this code:
UINavigationController *detailNavigationController =[[[self splitViewController] viewControllers] objectAtIndex:1];
[detailNavigationController pushViewController:"your_view_controller" animated:YES];
In your segue, set your style to "Push", and your destination to "Detail". Current will push the destination view controller onto your Master view, whereas Detail will push it into the "Detail" view. It's that simple. Then wire it up the same way you wire everything else up.
But be careful, if you don't implement a way for it to wait for a previous segue, you can get an "Unbalanced calls" error if a new Controller is pushed onto the detail view before it's done dismissing/pushing another one. Double tapping a cell in a table will do it.
I have a storyboard with tabbarcontroller. One of tab bar has a tableview and I want that when the user tap in a row from tableview open a detail view. The problem is when I open detail view tab bar and navigation bar hides... In the storyboard I create the detail view as a new view controller, then I create a new file and referred it to the class of detail view .
The code in didselectrowatindexpath:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
detalleYouTube *dvController = [[detalleYouTube alloc] init];
[self.navigationController pushViewController:dvController animated:YES];
}
Thank you in advance!
This is kinda old but if someone needs to do this here's an easy approach:
You can use add a segue from the view in the tab bar to detalleYouTube, put an identifier to the segue and do this:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[self performSegueWithIdentifier:#"segueIdentifier" sender:tableView];
}
Another approach to this is not to use tableView:didSelectRowAtIndexPath but instead use prepareForSegue:sender
the way I did it was:
-(void)prepareForSegue:(UIStoryboardSegue*)segue sender:(id)sender
{
DetailViewController *viewController = [segue destinationViewController];
CustomObject *custObject = [arrayOfObjects objectAtIndex:[self.tableView indexPathForSelectedRow].row];
viewController.objectNeeded = custObject;
}
This example is based on the idea that your detail view controller is connected to your table view controller.
I presume you have the 'Detail' view as part of the storyboard (not in a separate XIB), if so you will need to place a separate NavigationController at the start of the 'Detail' TabBarItem seque.
This page has a good tutorial on what I think your trying to achieve:
http://maybelost.com/2011/10/tutorial-storyboard-in-xcode-4-2-with-navigation-controller-and-tabbar-controller-part1/
Also check these links to a more in-depth Storyboard tutorial:
http://www.raywenderlich.com/5138/beginning-storyboards-in-ios-5-part-1
http://www.raywenderlich.com/5191/beginning-storyboards-in-ios-5-part-2
I am creating an app that uses a navigation controller.
In the the first view I have three different table views.
How can I make the tables open a new view in the navigation controller when one cell is selected?
Thanks
You implement tableView:didSelectRowAtIndexPath: as a delegate method on the view controller that shows the three table views, and in this method you initialize the new view controller you want to show and push it onto the screen by doing something like this:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// TODO: Find out what view controller to push based on the tableView and the indexPath.
UIViewController *viewController = [[YourSpecialViewController alloc] initWithNibName:nil bundle:nil];
[[self navigationController] pushViewController:viewController animated:YES];
[viewController release];
}