My problem seems like a generic problem, yet can't seem to find an answer for it.
I have a situation where when the user taps on a custom UITableViewCell, I would like to display an alert and then based on the response to the alert, either stay on the same view (user selecting cancel) or display another view (if the user selects proceed). And I would like to do this using the storyboard feature & segues.
How would one go about this? Do you have to do this the old fashioned way?
#user, Just create the alertView the old fashion way; I do know of any storyboard feature to do this differently. Where storyboard can help is with the segues. You can call the segues programmatically. With you alert view cancel button you can just return (i.e. do nothing). For the other option, to display another view, you can programmatically call a segue to transition to the desired view. If you don't have the proper segue already defined for some other reason on your storyboard, just create a button out and use that to create the segue and name it. Name the segue by clicking on it in storyboard and use the attributes inspector to give it name (identifier). Then hide the button or put it out of the view. I typically put these type of button on the toolbar and use spacers to keep them out of the view. Here's some sample code:
Call the segue from the alert view delegate like this:
[self performSegueWithIdentifier: #"done" sender: self];
Also implement this method to do any necessary task to prepare for the segue:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"done"])
{
// [[segue destinationViewController] setManagedObjectContext:self.managedObjectContext];
// [[segue destinationViewController] setSelectedClient:selectedClient];
}
}
You can create segues directly from the startingViewController to multiple destinationViewControllers that can then be "performed" programmatically. You do not need to create any hidden buttons for them, which does seem like a hack.
OK I came up with a solution in keeping with the storyboard that I like.
Example:
My tableview has 2 sections, grouped, and cells are dynamic prototype. Section 0 contains one row/UITableViewCell & I don't want it to segue. Section 1 contains multiple cells that I want to trigger the segue & drill down into the detail.
In Storyboard:
I removed the segue linking the tableviewcell to the destination view controller.
I made a 'generic' segue linking the source view controller directly to the destination view controller.
In the attributes on the segue, I set the identifier ('EditTimePeriod') and set the type to Push (I presume Modal would work just the same).
In the source view controller:
In the prepareForSegue method I handled both the common 'AddTimePeriod' segue I control-dragged from my UIBarButtonItem (Add), along with the 'generic'(vc-->vc) 'EditTimePeriod' segue.
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
// handle the click of the 'Add' bar button item
if([segue.identifier isEqualToString:#"AddTimePeriod"]) {
TimePeriodViewController* tpvc = (TimePeriodViewController*)segue.destinationViewController;
tpvc.delegate = self;
// database & entity stuff for adding the new one to the mOC, etc
}
// handle the click of one of the 'editable' cells -
if([segue.identifier isEqualToString:#"EditTimePeriod"]) {
TimePeriodViewController* tpvc = (TimePeriodViewController*)segue.destinationViewController;
tpvc.delegate = self;
TimePeriod * newTP = [self.timePeriodArray objectAtIndex:self.tableView.indexPathForSelectedRow.row];
tpvc.timePeriod = newTP;
}
}
Then I implemented the tableView:didSelectRowAtIndexPath method, and put my condition in here. If the selected row was outside of section zero I called the EditTimePeriod segue manually, defining the sender as the selected tableviewcell:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
if(self.tableView.indexPathForSelectedRow.section!=0){
[self performSegueWithIdentifier:#"EditTimePeriod" sender:[tableView cellForRowAtIndexPath:indexPath]];
}
[self.tableView deselectRowAtIndexPath:indexPath animated:YES];
return;
}
would be nice to code the cell in section 0 so that it is not selectable in the first place!
Hope this helps though.
** and then 5 minutes later I took another look and realized I could just move the data from section 0 into the section header, which is more intuitive and wasn't being used anyway. leaving the design open for a standard segue from each tableviewcell without needing any condition/check. Was a good exercise anyway though :)
Related
I have a table view with different types of table view cells in it. In one of the cells, there are two buttons, which load a view controller when pressed. I am using the following function to handle the button press:
- (IBAction)leftButtonPressed:(id)sender
{
// Getting the pressed button
UIButton *button = (UIButton*)sender;
// Getting the indexpath
NSIndexPath *indPath = [NSIndexPath indexPathForRow:button.tag inSection:0];
// Loading the proper data from my datasource
NSArray *clickedEvent = [[[SOEventManager sharedEventManager] eventsArray] objectAtIndex:indPath.row];
[[SOEventManager sharedEventManager] setSelectedEvent:clickedEvent[0]];
// Everything working as it should up to this point
// Performing seque...
[self performSegueWithIdentifier:#"buttonSegue" sender:self];
}
My buttonSegue is supposed to push a new view controller. Somehow instead of pushing once, it appears to be pushing twice, so I get the following warning:
2013-11-27 01:48:30.894 Self-Ordering App[2081:70b] nested push animation can result in corrupted navigation bar
2013-11-27 01:48:31.570 Self-Ordering App[2081:70b] Finishing up a navigation transition in an unexpected state. Navigation Bar subview tree might get corrupted.
In my case it leads to a crash, since there is an event in which I want the app to immediately pop the view controller so it an go back to my table view. I use an alertview for this and handle the event with the following:
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
NSString *buttonTitle = [alertView buttonTitleAtIndex:buttonIndex];
// ...
// Additional checking of button titles....
else if ([buttonTitle isEqualToString:NSLocalizedString(#"Vissza", nil)])
{
[self.navigationController popViewControllerAnimated:YES];
}
}
It might me interesting to note that I have an other segue from my "regular" table view cell, and in that case I use the prepareForSegue: method
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"detailSegue"])
{
NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
SOEvent *selectedEvent = [[[SOEventManager sharedEventManager] eventsArray] objectAtIndex:indexPath.row];
[[SOEventManager sharedEventManager] setSelectedEvent:selectedEvent];
}
}
In this case the view controller gets pushed perfectly, and even popped immediately if that is required. I am testing this on iOS7 and Xcode 5. I haven't encountered a problem like this before, any help would be greatly appreciated.
Maybe you want to wire up your segues with View Controllers instead of UIButtons!
You should probably have the segues wired with some button like this:
But you should wire them with the view controllers instead:
Answer by can poyrazoğlu:
are you sure you've wired up the actions correctly in interface
builder? maybe you've wired the event for, say, both touch up inside
and touch down inside instead of just touch up inside. or maybe you've
also assigned the segue from both code and again in interface builder.
have you checked them? it's a common mistake. –
I was assigning the touch up inside actions for each button both the storyboard and my tableview's datasource methods.
Thank you for your quick help can poyrazoğlu!!
For Swift 3, xcode, and ios 9+, which is what I am using: Make sure you are drawing segue from your UIViewControllers and not buttons or other interfaces.
I had the same problem, and simply changing the start of the segue from the UIController instead of the button removed this bug.
I always get this issue when I have my buttons directly wired up to the destination view controller. You need to make sure that you first delete the old segue you made, then click on the present view controller (where you are coming from) and CTRL + click to destination controller.
This should fix it :)
I've created an app with a navigation bar and a table view. When I press a entry in the table, I want it it take me to a new view to display some information on it. I've gotten it so that I can press a cell and call a function into the code. What I need to do in the other view is very simple, so I made it in interface builder, and I'd like to keep it that way. Is there any way that I can change to the view with the push animation to retain the navigation bar thing from the code, but while the view is made in interface builder? Thanks.
PS I don't know if it makes a difference, but I am making this in iOS7.
In interface builder, in your first view controller (containing table view), define a manual Segue, with identifier set as 'ToDetailView'.
Then in:
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[self performSegueWithIdentifier:#"ToDetailView" sender:self];
}
Additionally you can implement following to pass any need data to second view controller:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"ToDetailView"])
{
}
}
So I got a ViewController with 4 seperate buttons. When clicking on button1 TableViewController1 pops over the ViewController with a list of items. When selecting an item the TableViewController1 drops down and button1 now has the text that was selected in the table. This is all good. But when I do the exact same thing for button2 with TableViewController2 the data from button1 is reseted.
I use segues with identifiers, some of the code:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"showEducation"]) {
NSIndexPath *indexPath = [self.tableViewEducation indexPathForSelectedRow];
ViewController *destViewController = segue.destinationViewController;
destViewController.educationText = [tableViewArray objectAtIndex:indexPath.row];
}
}
So at the moment I got multiple segue identifiers for each button and multiple .h and .m files for the tableviews. Am I using a completely wrong technique to get this to work? I hope im clear enough, otherwise I can upload images.
Edit: I just noticed, I also have a slider on my ViewController. When clicking on a button and selecting a row in the TableView the slider gets reseted to the original position. Same problem as above kind of.
I am thinking that you're pushing to a new instance of your View Controller every time you push from either tableViewController.
Imagine that you click on one button on ViewController0, this creates an instance of tableViewController1. When you click a row, you're just using a performSegue to create a NEW instance of ViewController0, and this has its own ViewDidLoad - resetting the buttons.
(You're saying that the view "drops down", so it's modal?)
Don't use performSegue from the tableViewController back to the viewController, try using [self dismissModalViewController: withCompletion:](or something similar, can't remember), then your tableViewController should remove itself and reveal the original ViewController.
Now, you don't have a way to change the name of the button though, but that can be done by accessing the sender from the tableView, which will give you the original View Controller, and not a new instance of it.
One way of getting the sender is to use [performSegue... from ViewController0, and in it's own prepareForSegue, you could do something like
//In the first ViewController, not in the TableViewControllers
-(void)prepareForSegue:(UIStoryboardSegue*)segue sender:(id)sender{
if(sender == button1)
{
UITableViewController1 *dest = segue.destinationViewController;
[dest setSender: self];
}
}
And in TableViewController1 you'd create a variable ViewController *home;, and a method -(void)setSender:(ViewController*)sender;, so that in your didSelectRowAtIndexPath, you could now say [[(ViewController0*)home button1]setTitle:#..];, and then [dismissModalViewController..]
There are other ways to do it as well, depending on how you are pushing from your viewController to the tableViewController. And I'm sure there are easier ways to access the sender than this, but it works and is useful if you're already sending other data.
I'm totally lost in this issue. I have been working with storyboards, I have created a navigation controller with tableviews and some stuff. There are Services in each row of the tableview and I need to create one detail view for each service.
There are a lot of services, so I can't create them in the storyboard. The idea is to download the Services from a webservice (number of parameters, types of each one, etc..) and add as textfields / buttons as appropriate to the Service.
So, my problems and questions are:
1) Can I combine Storyboards and views programmatically? When I create a NewView in MyTableviewClass, should I do it in my prepareforsegue method? How can I show it in the screen without loosing my navigation controller? this is what I have (it doesn't work: it says to me that there is no segue with name 'Service1' ) :
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
if ([[segue identifier] isEqualToString:#"NextLevel"]) {
[segue.destinationViewController setActualNodo:[actualNodo getSonAtIndex:indexPath.row]];
} else if ([[segue identifier] isEqualToString:#"Service1"]) {
CGRect bounds = self.view.bounds;
UIView *myview = [[UIView alloc] initWithFrame:bounds];
[myview setBackgroundColor: [UIColor redColor]];
[self.view addSubview:myview];
[self.view bringSubviewToFront:myview]; }
Any book or reference is welcomed but I couldn't find anything similar. Is this very complicated in iOS? I have done a similar thing in Java. I have read about generating interfaces dynamically with XIBs but, sincerely, I don't know what it is..
Thanks for all.
Yes you can create a StoryBoard with a view and then add views programmatically to it.
You should not try creating a view within your prepareForSegue method. This really should be used for passing objects to another ViewController.
I would suggest this to you. Go back to your StoryBoard and create a new UIViewController scene. Click on your first scene and CTRL drag to the new scene. Next, click on your segue and give it a name.
Step 1:
Create a new class called ServicesViewController and make sure it's a subclass of `UIViewController:
Step 2:
Go back to your StoryBoard and click on scene so that it is selected. Next, click on the Files Owner and finally click on the class info button (the third button) and finally select your ServiceViewController class you just created.
Step 3:
Back in your ServicesViewController in the didSelectRowAtIndex method call your seque:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[self performSegueWithIdentifier:#"YOUR_SEGUE_NAME" sender:nil];
}
For now, clean out all the code in your prepareForSegue method and just get the transition down first.
In addition to Flea's answer, if you need to keep the navigation controller, just create a push segue in your storyboard by control dragging from the file owner icon (the yellow box under your view controller's view) of the table view controller to the ServiceViewController you added to the storyboard, this should show a popup window where you can select "push" as the type of the segue. Next, select the segue and in the attribute inspector (the fourth button, next to the one in the snapshot) and in the "Identifier" text field type in a unique identifier for your segue, such as serviceSegue.
At this point, using Flea's code:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[self performSegueWithIdentifier:#"serviceSegue" sender:nil];
}
And in the code you posted:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
if ([[segue identifier] isEqualToString:#"serviceSegue"])
[segue.destinationViewController setActualNodo:[actualNodo getSonAtIndex:indexPath.row]];
}
I'm not sure what you are trying to do with the other segue "Service1", but if you want to change the view of the TableViewController, segues are not the way to do it. If anything you should do it in the didSelectRowAtIndexPath method depending on the row selected:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (You want to transition to other view controller)
[self performSegueWithIdentifier:#"serviceSegue" sender:nil];
else
Change your view here.
}
I hope this helps!
It sounds like you just have multiple cells in a tableView. Instead of using segues you can simply create different cells with different identifiers and show or hide them based on what services are detected in your services array which is populated from your web service.
I have been able to find tutorials on creating the transition from one tableview to another view. The transition when you click on a cell is to the same destination view controller. I was wondering how I could transition to different view controllers for each of the cells in the tableview. Can I still do this with storyboard? If so, how? If not, what alternatives can you suggest?
The storyboard has been hooked up from the tableview to a detailed view. But this is what I would like to accomplish:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"mytransition"]) {
NSInteger sectionId = [[self.tableView indexPathForSelectedRow] section];
if (sectionId == 0) {
NSLog(#"try to change the destination view controller");
//I don't know if this is possible?!
} else {
NSLog(#"Proceed with the original destination view controller");
//this is ok
}
}
}
EDIT: I found the solution! You can view it here: http://forums.macrumors.com/showthread.php?t=1274743
In summary, the solution involved using a combination of the prepareForSegue and didSelectRowAtIndexPath for anyone who's interested. Instead of linking each cell to another view, create another segue from the controller to each of the desired view controllers. Then check the segue identifier.
-Jen
Jen's right. The solution from Jen's link does work.
Create the segue from the prototype cell in the table to the destination view controller.
Set the identifier of the segue.
Modify didSelectRowAtIndexPath to get the row number of the selected cell, and call performSegueWithIdentifier with the segue identifier for that cell.