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.
Related
Then I have my MasterViewController with its DetailViewController segued both.
I'm using Parse for backend platform, and I have a little problem for this: How to pass displaying datas of the MasterViewController to the DetailViewController
I'm using a NSArray called "RetrievingObjects" to retrieve it, For my Parse class querying it works successfully (I already used my query and my Array to display cells in MasterViewController), I use that method:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
DetailViewController *detailVc = [self.storyboard instantiateViewControllerWithIdentifier:#"detailVC"];
detailVC.lblDescription = [self.RetrievingObjects objectAtIndex:indexPath.row];
detailVC.lblTitleForDescr.text = [self.RetrievingObjects objectAtIndex:indexPath.row];
detailVC.dateForDescr.text = [self.RetrievingObjects objectAtIndex:indexPath.row];
[self performSegueWithIdentifier:#"goToDetailSg" sender:self];
}
It looks like you're not actually using detailVc anywhere. You should set it up in MasterViewController's prepareForSegue:sender: You can access what will become the DetailViewController through the segue object's destinationViewController property and set everything you need there.
Don't create the detail controller in code. If you have your segue set up correctly in the storyboard, that work will be done for you.
Instead, implement - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender, get the detail controller from the segue parameter, and pass the information you want it to display in that method.
Note that the destination controller's view will not have loaded at that point, so don't try to update IBOutlets.
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;
With this code every time user click on any cell will perform the segue again and again , I am wondering how could I keep track of the loaded view to keep data when switching views and not an infinite new viewcontroler.
Thanks -
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.row == 0) {
[self.navigationController
performSegueWithIdentifier:#"rep" sender:self];
} else if (indexPath.row == 1) {
[self.navigationController
performSegueWithIdentifier:#"rep1" sender:self];
}
}
Try my other approach first, but if you really need a maintain a pointer to the new view controller you could try this approach. This should perform the segue once, creating the reference to the view controller which will subsequently be manually pushed into the navigation controller.
Override the view controller methods:
- (BOOL)shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(id)sender{
if(self.myViewController == nil){
return YES;
}else{
[self.navigationController pushViewController:self.myViewController animated:YES]
}
}
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
self.myViewController = (MyViewController*)segue.destinationViewController;
self.myViewController.customVar = 1; //perform initial customization
}
What do I know though, I've never used Storyboards...
Perhaps an alternative to maintaining a reference to the view controller would be to customize the view controller prior to seque.
Override the view controller method:
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
MyViewController *targetController = (MyViewController*)segue.destinationViewController;
targetController.customVar = 1;
}
The default implementation of this method does nothing. Your view controller overrides this method when it needs to pass relevant data to the new view controller. The segue object describes the transition and includes references to both view controllers involved in the segue.
You don't need to take new View controller each and every time for each row unless and until you want it customized. This will make large amount of view controllers on storyboard.
So, Just command drag segue from controller A to B. Example: if A is tableViewController and B is simple VC where you are displaying data of table's row then command drag from whole tableViewController to B. Now this will act as common segue with one identifier only.
So in your code:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
//Set your B's label/property etc to cell's data or anything so that it will reflect in B.
B.label = cell.text;
[self performSegueWithIdentifier:#"Identifier" sender:self];
}
Hope this helps.
I think you can keep the new viewController's pointer. Then next time you can use it like this:
[self.navigationController pushViewController:thePointer animated:YES]
I very seldom use Storyboard. So I am not sure it will work.
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.
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