I have a pretty simple question. Say you have 2 view controllers A and B. A is a UITableView with STATIC CELLS. That is A is built using storyboard objects only as opposed to programmatically. B is also a UITableView but built programmatically with DYNAMIC CELLS. So I wired up Segues (with identifiers set in the storyboard) from each cells in A to the B Tableview.
Now what I would like to have is to know which segue has been pushed when a row in A is selected. I know this can easily be done if I create the cells in view A programmatically and use the prepare/perform segue methods. But since the contents in A will never change, I do not want to go that route. Reason why I am trying to find out how to check which segue has been pushed when I select a given row in A. Ideally there would be some for of a method DIDPERFORMSEGUE: (Segue identifier) I could call from the B Viewcontroller.
Thanks very much for your help and suggestions.
In your "A" TableView, you should be able to peek at which row was poked, and then push that information to your "B" table. For example:
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
if([[segue identifier] isEqualToString:#"UITableView_B"]){
BTableViewController *vc = (BTableViewController *)[segue destinationViewController];
NSIndexPath *path;
path = [self.tableView indexPathForSelectedRow];
[vc setSelectedPath:path];
}
}
Hope that helps. Best of luck.
Edit: Probably obvious, but in the above "self.tableView" is an outlet pointing at the UITableView.
Update
Personally, I'd bite the bullet and make a class for "A", but in the interest of hacking - it should be possible to reach back and get data from the previous view. This is 'bad code' (imo) and assumes you're using a Navigation Controller - and that the previous view is an "A" table, etc... Without further ado - is should be possible to just do this:
NSArray * views = [self.navigationController viewControllers];
NSUInteger prevViewIndex = [views count] - 2;
UIViewController * previousView = [views objectAtIndex:prevViewIndex];
ATableViewController * aTableViewController = (ATableViewController *) previousView;
NSIndexPath *path = [aTableViewController.tableView indexPathForSelectedRow];
I can't say I've needed to do this myself, but if you really don't want to change the source view controller or if you have a frequent need to identify the segue from the destination view controller, one way to do it would be to create your own UIStoryboardSegue subclass. A segue already knows about the destination view controller, so it's a simple matter to give the destination a chance to inspect the segue. Something like this should do the trick:
#interface MyStoryboardSegue : UIStoryboardSegue
#end
#implementation MyStoryboardSegue
- (void)perform
{
[super perform]
if ([self.destinationViewController respondsToSelector:#selector(didPerformSegue:)]) {
[self.destinationViewController didPerformSegue:self];
}
}
#end
It's not a great solution if you're already using UIStoryboardSegue subclasses for other reasons. And in fact, I haven't even tested the code, so perhaps there are some snags I haven't thought of yet. The point, however, is that if you want a segue to notify the destination when it executes, you can probably arrange it.
Related
Tell me, please, how should i transfer several variables from one ViewController to another?
For example, i have
ViewControlle.m
- (void) transferVar{
int a = 10;
int b = 11;
}
and i want to transfer them to another ViewController (SecondViewController.m). What should i do?
you can add a property on the second view controller for every variable you want to pass (or an array to contain them all), then if you are making the segue on the storyboard use the method
-(void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
and inside it make this
SecondViewController *svc = (SecondViewController *) segue.destinationViewController;
There you have the view controller you want to pass values so only save it
svc.yourArray = #[[NSNumber numberWithInt:a],[NSNumber numberWithInt:b]];
Also it is highly recommendable to add a segue identifier to the storyboard segue and check
if([segue.identifier isEqualToString: #"theIdentifier"])
to be sure it is the segue that you wanted
Now on the viewDidLoadMethod you can use them.
If you are not using the storyboard segue just assign the value after initiating the view.
I feel ignorant for asking this because I know it's simple. Goal: Retain a variable on a view after leave then returning to it.
For instance: Let's say we have MainView, CategoryView and (drumroll) ProjectView
Application opens to MainView it displays a table - user selects they want to pick a category. This segue's them to CategoryView. Once a selection is made I send the chosen category back to MainView. via
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
No problem here. MainView receives variable and displays the chosen category as subtext in the 'Category' section. Next the user wants to pick a project name (this is also a predefined list) They select the item and I send the variable back the MainView.
-- Now can somebody explain to me (gently) why when I return to the main view the NSString variable that was previously holding the 'chosen' category is now null?
So my NSString selectedProject is not being retained correct? What is the correct implementation I should be doing here? Or what am I missing? I'm really trying to understand whats going on so anything would be a great help.
MainView Interface
#interface MainViewController : UIViewController {
NSString *selectedProjectName;
NSString *selectedCategory;
}
#property (nonatomic, retain) NSString *selectedProjectName;
#property (nonatomic, retain) NSString *selectedCategory;
#end
MainView Implementation
#implementation MainViewController
#synthesize selectedCategory, selectedProjectName;
and if you need it..
ProjectView Implementation
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([[segue identifier] isEqualToString:#"masterSegue"]) {
MainViewController *vc = (MainViewController *)[segue destinationViewController];
LogValues *lv = [LogValues alloc];
lv.project = #"Test Project Name";
vc.selectedProjectName = lv.project;
}
}
Ok, your problem is that you're not returning to the same instance. You've missed one very very important thing about segues -- they ALWAYS instantiate new view controllers. So, when you use a modal segue to go "back" to the main view controller, you're just creating a new instance of that controller. You should never go backwards (to earlier controllers) in a storyboard using anything other than an unwind segue. Unwinds are the exception to the rule about segues always creating new instances.
So, you have 2 choices. You can either use an unwind segue, which is nice, because you can still implement prepareForSegue to send data back to the destination controller, or you can not use a segue at all, and use dismissViewControllerAnimated:completion: to go back, and use a delegate to send back the data.
So of course immediately after posting I see a thread in the side that I believe helps. The solution was to store the values in the AppDelegate which is working fine.
I feel however there are other ways to achieve the same result without having to rely on the AppDelegate and would love to hear other solutions.
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 am using Storyboard in my app and I want to pass data from one view to another view.
Instead of using segues I am using instantiateViewControllerWithIdentifier. In this case I am instantiate from my first TableViewController to a NavigationController which has a second TableViewController attached because I need the navigation in the second TableViewController. Now I want to pass data from my first TableviewController, depending which row was clicked, to my second TableviewController. In this case newTopViewController would be my NavigationController but my problem is now how to pass data from firstTableViewController to the secondTableviewController.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *identifier = [NSString stringWithFormat:#"%#Top", [menuArray objectAtIndex:indexPath.row]];
UIViewController *newTopViewController = [self.storyboard instantiateViewControllerWithIdentifier:identifier];
}
If you instantiate a navigationController, you can use the viewControllers property to get the inner viewController of the navigation controller.
Something like this:
UINavigationController *navigationController = [self.storyboard instantiateViewControllerWithIdentifier:identifier];
MBFancyViewController *viewController = navigationController.viewControllers[0];
// setup "inner" view controller
viewController.foo = bar;
[self presentViewController:navigationController animated:YES completion:nil];
newTopViewController.anyVariableToShow= anyVariableToSend;
I do this pretty often on a few of my apps...
//Create new VC
CookViewController *detailViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"CookVC"];
//Set recipe
[detailViewController setRecipe:recipe];
//Pop over VC (can be pushed with a nav controller)
[self presentPopupViewController:detailViewController animationType:MJPopupViewAnimationFade];
If you aren't using a navigation controller or segues, then I think you need to reconsider your app design.
Actually it's not just a data pass problem as this is a program control and data transfer question together.
Even you would have to rethink about your app's concept, as you'd like to use storyboard without the meaning of storyboard, it's up to you and I hope you have good reason to do what you do.
So when you decided not to use segue you lost the new and comfortable way of instantiating a new controller and transferring data with it and you have to do the transfer of control and the data in two distinct steps. When you instantiate another scene in storyboard (like you do with instantiateViewControllerWithIdentifier:) you just instantiated a new controller and transferred the control but not the data. Just think about it as you instantiated a new controller from a xib in an old way (so you have to use initWithCoder: or awakeFromNib in the second view controller as the storyboard will not call initWithName:bundle:), but did not do anything more.
So you will have a new controller (it named in the identity part of the second storyboard) which is hanging in the universe without any relationship or connection with anything else (as the storyboard picture illustrates it nicely) and you could do with it what you'd like.
So you'd like to do something and you need data from the previous storyboard (ViewController). What you need is making available those data to the second storyboard(ViewController), and as you know there are lot of solution for this which were available long time before even storyboard is existed.
So regarding your code, the "data transfer" is depending on your design, whether the two controllers are subclasses of each other or whatsoever...
If you don't like to deal with subclassing and like to decoupling them as much as possible, the best way just make a property of your data in the first controller and refer to them from the second (after importing the first's .h file) and just refer to it in it's viewDidLoad or in initWithCoder: or anywhere where you need them, as
secondViewControllerdata = firstViewControllerdata.thatDataProperty
Of course you can do the same in reverse and make a property of the second controller and refer to it in your first view controller.
You can define some parameter in UIViewController to receive data:
#property (assign) int param1;
#property (retain) NSMutableArray *param2;
and use below to pass the data:
[newTopViewController setParam1:XX];
[newTopViewController setParam2:XX];
I'm working with the Master-Detail project template that comes with Xcode and referenced in http://developer.apple.com/library/ios/#documentation/iPhone/Conceptual/SecondiOSAppTutorial/
Problem: I am trying to figure out how to add additional UIViewControllers to the default UINavigationController that this template comes with.
Specifically, I would like to add a DetailEditViewController after DetailViewController. Here is what I've done to this effect so far:
In DetailViewController I added an edit button to the navigationItem:
- (void)viewDidLoad
{
[super viewDidLoad];
self.navigationItem.rightBarButtonItem =
[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemEdit
target:self
action:#selector(editDetailItem:)];
[self configureView];
}
You can see it specifies a message selector editDetailItem:, which I've implemented as:
- (void)editDetailItem:(id)sender
{
[self.navigationController pushViewController:
[[DetailEditViewController alloc] init] animated:YES];
}
I've created a DetailEditViewController on the Storyboard, and the code runs without crashing, producing a black, blank window with a navigation item to take me back to detail. From here on I am pretty confused:
When I drag a new View Controller to the Storyboard, no corresponding code files are created! Am I responsible for making code files for these controllers? I see that Storyboard View Controllers are associated with a Class in the Identity Inspector... but why on earth would it not create templates for a new UIViewController when I drag one onto the Storyboard?
Should I be using a seque instead of -pushViewController to get from DetailViewController to DetailEditViewController? If so, I'm not sure how to add one on the Storyboard, because the navigationItem's UIBarButtonItems are all added in-code. There's nothing to Ctrl-drag from.
How do I send information from DetailViewController to DetailEditViewController? When MasterViewController segues to DetailViewController, it specifies the sender via - prepareForSegue:sender:
You're right, no corresponding files are produced. How is the system supposed to know what class you want? You need to create a UIViewController subclass, and change the class of the controller you drag in, to that class. The easiest way to push the new controller is to use a push segue -- if you don't have a UI element in the storyboard to connect that to, you connect it directly from the controller and give the segue an identifier (which I call "GoToEdit" in my example). In the action method for the edit button, then perform the segue:
[self performSegueWithIdentifier:#"GoToEdit" sender:self];
If you want to pass information, then you implement prepareForSegue:, something like this:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"GoToEdit"]) {
NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
NSDate *object = _objects[indexPath.row];
[[segue destinationViewController] setDetailItem:object];
}
}
It's a good thing to check the segue identifier first. Then you can access your destinationViewController (you might have to cast it to your class, so the compiler will recognize any property of it you're trying to set), and pass what you want to it.