Passing data between two controllers using storyboards - iphone

I am trying to pass data from one UITableViewController to another. This is my code in the initial view controller:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
Subject *subject = (Subject *)[self.fetchedResultsController objectAtIndexPath:indexPath];
[self showList:subject animated:YES];
[self.tableView deselectRowAtIndexPath:indexPath animated:YES];
}
- (void)showList:(Subject *)subject animated:(BOOL)animated {
ListsViewController *lists = [[ListsViewController alloc] initWithStyle:UITableViewStyleGrouped];
lists.subject = subject;
NSLog(#"%#", lists.subject);
[self performSegueWithIdentifier:#"showDetail" sender:self];
}
The log output was showing that it had the data I wanted had been passed over. However when I perform the segue and log the subject in the ListsViewController is shows null.
Any ideas?

You need to overwrite prepareForSegue:sender: method. The quick fix would be
- (void)showList:(Subject *)subject animated:(BOOL)animated
{
[self performSegueWithIdentifier:#"showDetail" sender:subject];
}
...
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"showDetail"]) {
ListsViewController *controller = ([segue.destinationViewController isKindOfClass:[ListsViewController class]]) ? segue.destinationViewController : nil;
controller.subject = ([sender isKindOfClass:[Subject class]]) ? subject : nil;
}
}
The reason why your code did not work is that in your showList:animated: method you created a ListsViewController instance and assigned it a subject, but this view controller was never presented. Instead performSegueWithIdentifier:sender creates another instance of your ListsViewController class which knows nothing about your subject. That's why you need to wait for UIStoryboardSegue to instantiate a destination view controller from the storyboard and then configure it the way you want, which you can do in prepareForSegue:sender: method.
Also it might be not the best idea to use subject as a sender in performSegueWithIdentifier:sender method, because it's just not the sender :). What I would do is create a property subject in your view controller class and use it prepareForSegue:sender:
#interface MyViewController ()
#property (strong, nonatomic) Subject *subject;
#end
#implementation MyViewController
- (void)showList:(Subject *)subject animated:(BOOL)animated
{
self.subject = subject;
[self performSegueWithIdentifier:#"showDetail" sender:self];
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"showDetail"]) {
ListsViewController *controller = ([segue.destinationViewController isKindOfClass:[ListsViewController class]]) ? segue.destinationViewController : nil;
controller.subject = self.subject;
}
}
...
#end

Implement prepareForSegue and pass date in this methos
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if([seque.identifier isEqualToString:#"showDetail"])
{
ListsViewController *lists = seque.destinationViewController;
lists.subject = subject;
}
}

That's good, but now you need to add this:
First instead of:
[self performSegueWithIdentifier:#"showDetail" sender:self];
You need to send the object:
[self performSegueWithIdentifier:#"showDetail" sender:subject];
Add a property in your ListsViewController.h:
#property (nonatomic, strong) Subject * subjectSegue;
And now in your first view controller:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"showDetail"]) {
ListsViewController * lists = (ListsViewController *)[segue destinationViewController];
lists.subjectSegue = sender;
}

You need to understand that performSegueWithIdentifier:sender: creates a new instance of view controller. So the ListsViewController that you have created is not the being displayed on the screen.
You need to override `prepareForSegue:sender:
- (void)showList:(Subject *)subject animated:(BOOL)animated
{
[self performSegueWithIdentifier:#"showDetail" sender:self];
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"showDetail"]) {
ListsViewController *controller = (ListsViewController *)segue.destinationViewController;
controller.subject = self.subject;
}

Related

[UINavigationController setGoalName:]: unrecognized selector sent to instance 0x7964e2c0

I have created the app with following code. Its working fine with iOS7 but it throws the below error when I run with iOS8.
[UINavigationController setGoalName:]: unrecognized selector sent to instance 0x7964e2c0
My firstViewcontroller.m
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
GoalDetailsViewController *goalsDetailsViewController = segue.destinationViewController;
NSLog(#"%#",[NSString stringWithFormat:#"%#", [[self.arrCategoryTitle objectAtIndex:indexPath.row] objectAtIndex:indexOfCategory]]);
goalsDetailsViewController.goalName = #"Exercise Daily";
}
My GoalDetailsViewController.h
#interface GoalDetailsViewController : UIViewController <UITableViewDelegate, UITableViewDataSource>
#property (nonatomic) NSString *goalName;
Thanks in advance.
Seems like your destinationviewcontroller is a subclass of UINAvigationController.
Try this:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
GoalDetailsViewController *goalsDetailsViewController = [(UINavigationController*)segue.destinationViewController topViewController];
NSLog(#"%#",[NSString stringWithFormat:#"%#", [[self.arrCategoryTitle objectAtIndex:indexPath.row] objectAtIndex:indexOfCategory]]);
goalsDetailsViewController.goalName = #"Exercise Daily";
}
The easiest way to handle this crash would be to simply make sure that the destinationViewController is of the type you're expecting before you attempt to set a property on it. Something like this:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
GoalDetailsViewController *goalsDetailsViewController = segue.destinationViewController;
NSLog(#"%#",[NSString stringWithFormat:#"%#", [[self.arrCategoryTitle objectAtIndex:indexPath.row] objectAtIndex:indexOfCategory]]);
if ([segue.destinationViewController isKindOfClass:[GoalDetailsViewController class]]) {
GoalDetailsViewController *goalsDetailsViewController = segue.destinationViewController;
goalsDetailsViewController.goalName = #"Exercise Daily";
}
}
This change ensures that the destinationViewController is of kind GoalDetailsViewController before treating it as such.

UITableView prepareForSegue assigning indexPath to sender?

In the code below I am just trying out setting up a segue directly from the UIViewController to a DetailViewController. I usually do the segue from the UITableViewCell (which I think is clearer) but this time I just wanted to try going directly from the ViewController using -tableView:didSelectRowAtIndexPath:
My question is: I got to a situation where I needed to access the UITableViewCell indexPath from within -prepareForSegue:sender: my solution was to send the indexPath via sender. Usually I would set sender to self here, I have looked around and I can't find any reference to this, I am just curious if using sender to pass an indexPath is acceptable? I am pretty sure it is, and I am sure I have read that you can somewhere before, but I can't find any reference to it now.
// ------------------------------------------------------------------- **
// DELEGATE: UITableView
// ------------------------------------------------------------------- **
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSLog(#"%s", __PRETTY_FUNCTION__);
[self performSegueWithIdentifier:#"SEGUE_TWO" sender:indexPath];
}
// ------------------------------------------------------------------- **
// SEGUE: ViewController_ONE > DetailViewController
// ------------------------------------------------------------------- **
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
NSLog(#"%s", __PRETTY_FUNCTION__);
if([[segue identifier] isEqualToString:#"SEGUE_TWO"]) {
NSUInteger cellRow = [(NSIndexPath *)sender row];
DetailViewController *destinationController = [segue destinationViewController];
[destinationController setDataString:[[self dataList] objectAtIndex:cellRow]];
}
}
EDIT: What I was looking for in this case was
NSIndexPath *indexPath = [[self tableView] indexPathForSelectedRow];
essentially a way to get indexPath into -performForSegue:sender: without having to pass it via the sender parameter (in -performSegueWithIdentifier:sender:)
I typically avoid using the sender in prepareForSegue: because it could be anything. This makes asserting that your sender is what you are expecting more cumbersome and fragile. Also, I prefer to check for the class of the destinationViewController object rather than the segue name. Your functionality is tied to your object (or more specficially its interface), not the title of your segue, so checking your class makes it clear what you are trying to accomplish.
Below is the usual template I use in production when segueing from a UITableView. Note that you should always check if the destinationViewController is embedded in a nav controller and then check the class of the object as well to make sure you are handling it correctly. Finally, use an assert to ensure that somewhere down the line you haven't added some new segue that you forgot to handle.
- (void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
// unwrap the controller if it's embedded in the nav controller.
UIViewController *controller;
UIViewController *destVC = segue.destinationViewController;
if ([destVC isKindOfClass:[UINavigationController class]])
{
UINavigationController *navController = (UINavigationController *)destVC;
controller = [navController.viewControllers firstObject];
}
else
{
controller = destVC;
}
if ([controller isKindOfClass:[DetailViewController class]])
{
DetailViewController *vc = (DetailViewController *)controller;
NSIndexPath *ip = [self.tableView indexPathForSelectedRow];
[vc setDataString:[[self dataList] objectAtIndex:ip.row]];
}
else
{
NSAssert(NO, #"Unknown segue. All segues must be handled.");
}
}
You could use UITableView's method indexPathForCell. I hope you have a tableView instance variable.
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if([sender isKindOfClass:[UITableViewCell class]]) {
NSIndexPath * indexPath = [self.tableView indexPathForCell:sender];
//Your code here
}
}

Segue after login with facebook ios 6

I'm using Facebook SDK 3.1, my login system is ok, after login in I want to go to other ViewController. I connected both with a segue and the identifier, but now I don't know how I can implement it.
Here, after this:
if (appDelegate.session.isOpen) {
[self.buttonLoginLogout setTitle:#"Log out" forState:UIControlStateNormal];
[self.textNoteOrLink setText:[NSString stringWithFormat:#"https://graph.facebook.com/me/friends?access_token=%#", appDelegate.session.accessTokenData.accessToken]];
}
On my TableViewCell example I'm using it like this:
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"test"]) {
NSIndexPath *selectedIndexPath = self.tableView.indexPathForSelectedRow;
RestauranteDetalhesViewController *destViewController = segue.destinationViewController;
destViewController.idRest =[bd.idrestauracao objectAtIndex:selectedIndexPath.row];
}
But here I don't have a indexpath to give to the (id)sender.
Hey Fabio Cardoso there are two types of Segues
Trigger less segue and
Triggered segue
In trigger less segue you just directly drag from one ViewController to another. You dont mention on which action you want the Segue to be done. To do this you need to create segue identifier like you did above. And call the below line in your code.
[self performSegueWithIdentifier:#"YOUR_IDENTIFIER" sender:self];
In second way of segue you directly Ctrl+drag from a button to another ViewController. So no need of writing any code for this. When ever you click on that button it will either do Push, Model or custom segue what ever you selected in storyboard.
When a segue if performed below method is called in the Source ViewController. Here you can write you own code to either pass value from first ViewController to another or any other stuff.
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
If there are more than one segue from your ViewController than the identifier will be of use. You can use the identifier like this
if ([segue.identifier isEqualToString:#"YOUR_IDENTIFIER"])
Note: Segue is only one way, so if you create segue from first ViewController to another and if you want to come back (In case of model action) you have to write the delegate method for second ViewController and on clicking the button action call
**FirstViewController.h**
#interface FirstViewController : UIViewController <SecondViewControllerDelegate>
{
}
- (IBAction) buttonTapped:(id)sender;
**FirstViewController.m**
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"YOUR_IDENTIFIER"])
{
SecondViewController *svc_obj = segue.destinationViewController;
svc_obj.delegate = self;
}
}
- (void) buttonTapped:(id)sender
{
[self performSegueWithIdentifier:#"YOUR_IDENTIFIER" sender:self];
}
- (void) secondViewControllerDidCancel: (SecondViewController *) controller
{
[controller dismissViewControllerAnimated:YES completion:nil];
}
**SecondViewController.h**
#class SecondViewController;
#protocol SecondViewControllerDelegate <NSObject>
- (void) secondViewControllerDidCancel: (SecondViewController *) controller;
#end
#interface SecondViewController : UIViewController
{
}
#property (nonatomic, weak) id <SecondViewControllerDelegate> delegate;
- (IBAction)cancelButtonTapped:(id)sender;
**SecondViewController.m**
- (IBAction)cancelButtonTapped:(id)sender
{
[self.delegate secondViewControllerDidCancel:self];
}
#end
I resolved my problem with this simple line:
[self performSegueWithIdentifier:#"begin" sender:self];

UITextView doesn't update text after segue

I'm trying to update a UITextView's text property, after a segue, which happens after a row of a table view is tapped.
here is my code
MasterViewController.m
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
if ([[segue identifier] isEqualToString:#"4 Choices"]) {
NSLog(#"prepareForSegue is called");
NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
NSManagedObject *selectedObject = [[self fetchedResultsController] objectAtIndexPath:indexPath];
NSString *questionContent = [[selectedObject valueForKey:#"questionContent"]description];
self.questionContent = questionContent;
[segue.destinationViewController setTextViewWith:self.questionContent];
}
}
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(#"didSelectRowAtIndexPath is called");
NSManagedObject *selectedObject = [[self fetchedResultsController] objectAtIndexPath:indexPath];
NSNumber *questionID = [selectedObject valueForKey:#"qID"];
int i = [questionID intValue];
if (i == 1) {
[self performSegueWithIdentifier:#"4 Choices" sender:self];
}
else
{
[self performSegueWithIdentifier:#"5 Choices" sender:self];
}
}
DetailViewController.m
-(void)setTextViewWith:(NSString *)aText
{
self.questionText = aText;
NSLog(#"setTextViewWith is called");
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self.questionView setText:self.questionText];
NSLog(#"viewWillAppear is called");
}
EDIT 1 : change the code to set the text at 'viewWillAppear'
EDIT 2 : add 'didSelectRowAtIndexPath'
Note : the segue was pointed from MasterViewController to DetailViewController (not from the
tableViewCell)
Thanks for your help : )
The text view may not be loaded at the prepareForSegue point. I've only just started with storyboards and have found that I had to set string properties on the destination view controller instead, then update the UI components in viewDidLoad or viewWillAppear.

How To Use Storyboard?

I'm trying to use storyboards segue instead of didSelectRowAtIndex.
This is what I have, but this method does not recognize indexPath.row. Is there an alternative I can use to do the same thing?
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
SpecificExerciseTableViewController *upcomingViewController = segue.destinationViewController;
if ([segue.identifier isEqualToString:#"Web"])
{
upcomingViewController.exerciseArray = [[self.muscleArray objectAtIndex:indexPath.row]objectForKey:#"exercises"];
upcomingViewController.muscleName = [[self.muscleArray objectAtIndex:indexPath.row]objectForKey:#"muscleName"];
}
}
Unless you have that indexPath as an ivar, no such variable exists in the scope.
If you would like to get the indexPath for the selected row in a UITableView use:
- (NSIndexPath *)indexPathForSelectedRow
The adjusted code for above:
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
SpecificExerciseTableViewController *upcomingViewController = segue.destinationViewController;
if ([segue.identifier isEqualToString:#"Web"])
{
upcomingViewController.exerciseArray = [[self.muscleArray objectAtIndex:indexPath.row]objectForKey:#"exercises"];
upcomingViewController.muscleName = [[self.muscleArray objectAtIndex:indexPath.row]objectForKey:#"muscleName"];
}
}
This trick works fine,heres an example of how I used it to pull parsed elements into a web view
.h file
#property (strong, nonatomic) NSString *url;
#property (strong, nonatomic) UIWebView *webView;
.m file
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
if ([segue.identifier isEqualToString:#"WebViewSegue"]) {
WebViewController *webViewController = segue.destinationViewController;
webViewController.url = (self.parseResults)[indexPath.row][#"link"];
webViewController.title = (self.parseResults)[indexPath.row][#"title"];
}
}