Pass data between view controller [duplicate] - ios

This question already has answers here:
Passing data between view controllers
(45 answers)
Closed 7 years ago.
I'm trying to pass an object (PFObject) from an view controller to another,
-(void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control
{
RestauCardViewController *restauCard = [[RestauCardViewController alloc]init];
RestaurantAnnotation *restoAnnotation = (RestaurantAnnotation *)view.annotation;
restauCard.restaurant = restoAnnotation.restaurant;
[self performSegueWithIdentifier:#"segueToCard" sender:nil];
}
When I try to display the object in the other view controller I got null:
#import <UIKit/UIKit.h>
#import <Parse/Parse.h>
#interface RestauCardViewController : UIViewController
#property(nonatomic) PFObject *restaurant;
#end
This is my viewDidLoad function
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
NSLog(#"The restaurant name is : %#",_restaurant[#"nom"]);
}

You need to use -prepareForSegue to manage this situation, and you'll need an iVar to keep the restaurant name.
So in the top of your map's .m file, add an ivar NSString
#implementation yourViewController{
NSString *sRestName; //This is empty until the user selects a restaurant
}
-(void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control
{
sRestName = //Set the name of your restaurent here, it's just a string.
//You could set any other type of object (a restaurent object or a PFOjbect or anything,
//just change the ivar accordingly
[self performSegueWithIdentifier:#"segueToCard" sender:nil];
}
What you're going to do is replace your old code with the code just above, you just need to perform the segue to call the following method.
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if([segue.identifier isEqualToString:#"fromHomeToList"]){
RestauCardViewController *vc = (RestauCardViewController*)segue.destinationViewController;
vc.restaurant = sRestName; //here you're just giving the property of the new controller the content of your ivar.
}
That way you can pass an object from your map tap to your next controller. You're also sure it will never be nil because the user tapped it ; if it was nil, well, he couldn't have tapped it in the first place !
Note that I assumed you were using a string for your restaurant name, but if you change the ivar on the top, you can use anything you want, as long as you can retrieve it from tapping on the map. If you can't, I need more details to walk you through another solution.
Ask me if you have any quesiton, otherwise this should work !

You have to set the restaurent inside the UIViewController method "prepareSegue...". It's called after you performeSegueWithIdentifier, so the destination controller is accessible and you can test the segue.identifier and set the restaurent to the controller.
Example :
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"segueRestaurentDetails"]) {
RestauCardViewController *destController = (RestaurentDetailsViewController *)segue.destinationViewController;
destController.restaurant = (RestaurantAnnotation *)view.annotation;
}

Implement prepareForSegue:sender: method in ViewController
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if([segue.identifier isEqualToString:#"segueToCard"]) {
RestauCardViewController *controller = (RestauCardViewController *)segue.destinationViewController;
controller.restaurant = (RestaurantAnnotation *)view.annotation;
}
}

Related

passing nesting through segue nil on init methods but valid on viewdidload

I have made a segue passing a string which tells the next view controller which instance to parse the CoreData for. The problem is, I am using some code that calls init methods and the sent string is not initialized when it is called. However, the segue is working when I display the string in the destination view controller's viewDidLoad
- (id)init
{
self = [super init];
if (self)
{
[self initFakeData];
}
return self;
}
When that initFakeData method is called it sets up a graph and needs the exercise to hold a valid value
- (void)initFakeData
{
NSString *myExercise=exercise; //returns nil
if (myExercise==nil)
{
myExercise=#"Default";
}
}
Meanwhile...
-(void)viewDidLoad{
NSString *myExercise=exercise; //returns value
}
exercise is a property that is initialized by the previous view controller in a tableview
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
if ([segue.identifier isEqualToString:#"showGraph"]) {
JBLineChartViewController *destViewController = segue.destinationViewController;
NSString *myExericse=[NSString stringWithFormat:#"%#", [[_exercises valueForKey:#"exercise"]objectAtIndex:indexPath.row]];
NSLog(#"%#",myExericse);
destViewController.exercise = myExericse;
}
}
The behaviour is correct because during init the exercise in JBLineChartViewController was not set. If you need the exercise attribute in the init method to set certain behaviour that have to be before viewDidLoad, my suggestion is to not use segue but do a designated initWithExercise and push the controller in code. Maybe like this:
- (IBAction)chartButtonPressed:(id)sender {
JBLineChartViewController *vc = [[ShopViewController alloc]initWithExercise:#"EXERCISE_STRING_HERE"];
[self showViewController:vc sender:self];
}
The new view controller is allocated and initialized before prepareForSegue is called. Anything you need to do with CoreData should be done in viewDidLoad. Or you can do it later, e.g. in viewWillAppear or viewDidAppear.

passing data to another view controller when tapping on annotation

I have an app that displays locations on map. I want to pass the data from the 1st view (MapViewController) to the another view (locationDetailViewController)
I have used the code below to pass the data and it takes me to another second view. however, it doesn't pass the mutable array to another view controller...
- (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control
{PlaceDetailViewController *det=[[PlaceDetailViewController alloc]init];
det.PlaceDetailMutableArray=PlaceMutableArray;
[self performSegueWithIdentifier:#"DetailView" sender:view];}
thanks in advance
Try this:
On your SecondViewController.m, add the method (Don't forget to declare it on your SecondViewController.h too):
-(void)setValue:(NSMutableArray*)array
{
NSMutableArray *PlaceDetailMutableArray = [[NSMutableArray alloc] init];
PlaceDetailMutableArray = array;
}
And on the first ViewController, add the code below:
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"DetailView"]) {
PlaceDetailViewController *det= segue.destinationViewController;
[det setValue: PlaceMutableArray];
}
}
Finally, change the code of your example for:
- (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control
{
[self performSegueWithIdentifier:#"DetailView" sender:view];
}

Trying to implement a delegate that signals when the back button is pressed in the navigation bar - what am I doing wrong?

I have a main list of articles, and upon clicking one it segues to a reading view controller, and there I keep track of the progress of the user's reading with that view controller having an NSNumber property holding the position. I want to update this position back to the root view controller when they press the back button (so I can show them their progress) but my delegate doesn't seem to be working.
In the reading view's .h file:
#property (nonatomic, weak) id<UpdateProgressDelegate> delegate;
...
#protocol UpdateProgressDelegate <NSObject>
#required
- (void)finishedReadingWithPosition:(NSNumber *)position;
#end
In the .m file:
- (void)viewDidDisappear:(BOOL)animated {
[super viewWillDisappear:YES];
if ([self.delegate respondsToSelector:#selector(finishedReading:)]) {
[self.delegate finishedReadingWithPosition:self.position];
}
}
In my root view (note it does indeed implement the protocol):
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"ReadBasicArticleSegue"] || [segue.identifier isEqualToString:#"ReadFullArticleSegue"]) {
ReadingViewController *destination = segue.destinationViewController;
NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
self.rowOfLastSelectedCell = #(indexPath.row);
Article *article = self.articles[[self.rowOfLastSelectedCell intValue]];
// Set ReadingViewController's variables so the selected article can be read
destination.textToRead = [article.body componentsSeparatedByString:#" "];
destination.wordsPerMinute = #(1500);
destination.numberOfWordsShown = #(3);
destination.delegate = self;
}
}
and...
- (void)finishedReadingWithPosition:(NSNumber *)position {
Article *article = [self.articles objectAtIndex:[self.rowOfLastSelectedCell intValue]];
article.position = position;
[self.tableView reloadData];
}
I just don't see what I'm doing wrong. When I press the back button, the root view controller still has a 0% progress indicator.
Here:
- (void)viewDidDisappear:(BOOL)animated {
[super viewWillDisappear:YES];
if ([self.delegate respondsToSelector:#selector(finishedReading:)]) {
[self.delegate finishedReadingWithPosition:self.position];
}
}
viewDidDisappear: should pass the same 'did' method to super, not viewWillDisappear: ..
[super viewDidDisappear:animated];
The selector finishedReading: is not the same as the selector finishedReadingWithPosition:. As it is not implemented in the delegate, the conditional is not called.
Typos - or the solution...?
two quick things:
first, in your delegate respondstoSelector check, make sure you are testing for the correct method. You have "finishedReading:" in the check, then you call finishedREadingWithPosition:" in the method call. My guess is that it's skipping that line because its the wrong selector your checking for.
next make sure self.position have a value
and also:
I agree on the [super] call that #He Was mentioned - needs to be a call for the same method

iOS: How to load an object in a mapview depending on which annotation was tapped?

I have an app with a mapview containing various building objects (my own class). Each building has an annotation property which all works great and the user can search a table for the desired building, tap a button and it'll show the relevant annotation.
Now I have a callout button on the annotation which calls a method (showDetails) that pushes a segue to the DetailViewController, but the issue I'm having is getting the right building across to this DetailViewController. All the buildings are stored in an array in the data controller and I'd like to load one from that if possible.
So far I've used the controlWasTapped method where I've simply put:
- (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control
for (Building* theBuilding in self.datacontroller.masterArray){
if(theBuilding.name isEqualToString:view.annotation.title){
NSLog(#"theBuilding's name %#", theBuilding.name);
}
}
This works fine and returns the correct building but I'm stumped as to how to get this to the DetailViewController. I have a PrepareForSegue method but how would I get the correct building to it? Also, now that I have DidSelectAnnotation, is there any need for my showDetails method?
I was thinking I could modify the showDetails method so that it took an argument of Building type and then I could provide the Building details in prepareForSegue but if there is a much better way I'd love to know.
So, what's the best plan? Many thanks
Declare an instance variable in the class that contains the code you posted.
Building *buildingOfInterest;
then
- (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control
for (Building* theBuilding in self.datacontroller.masterArray)
if(theBuilding.name isEqualToString:view.annotation.title) {
buildingOfInterest = theBuilding;
[self performSegueWithIdentifier:#"ShowBuildingSegue" sender:self];
break;
}
}
then:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([[segue identifier] isEqualToString:#"ShowBuildingSegue"])
((MyBuildingVC *)segue.destinationViewController).building = buildingOfInterest;
}
and in your target view controller's header file:
#interface MyBuildingVC : UIViewController
#property (strong) Building * building;
#end

why does my app crash when my masterview controller tries to launch my detail view?

I am trying to make my first iOS app after doing a few tutorials. I am making a simple headline/news reader that pulls New Orleans Saints headlines and stories as json from an ESPN API, displays the headlines in a table view, and then displays the text of the story in a detail view when the user taps a headline.
The app compiles and runs without errors or warnings. The prepareForSegue method executes when the user taps a headline in the top tableview. I can step thru this code in the debugger. If I step thru the prepareForSegue method, it calls the detailViewController's setStory method, which in turn calls the configureview method. But then it takes me to main.m and the app closes, without showing the detail view.
I am a noob. What am I missing? Why don't I see the detail view? Maybe I need an IBAction to load the IBOutlet?
saintsMasterViewController.m
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"showDetail"]) {
saintsDetailViewController *detailViewController = [segue destinationViewController];
saintsNewsStory* displayThisStory =[self.dataController objectInListAtIndex:[self.tableView indexPathForSelectedRow].row];
[detailViewController setStory:displayThisStory];
}
}
saintsDetailViewController.m
- (void)setStory:(saintsNewsStory *)newStory
{
// if (_story != newStory) {
_story = newStory;
// Update the view.
// [self configureView];
// }
}
- (void)configureView
{
self.storyText.text = self.story.storyText;
}
- (void)viewDidLoad //the app never gets here. if i put a breakpoint here it never gets here
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self configureView];
}
saintsDetailViewController.h
#class saintsNewsStory;
#interface saintsDetailViewController : UIViewController
#property (weak, nonatomic) IBOutlet UITextView *storyText;
#property (retain, nonatomic) saintsNewsStory *story;
#end
The console shows this error:
SaintsHeadlineReader[9608:11303] * Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[ setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key detailDescriptionLabel.'
* First throw call stack:
(0x1c90012 0x10cde7e 0x1d18fb1 0xb7a711 0xafbec8 0xafb9b7 0xb26428 0x2320cc 0x10e1663 0x1c8b45a 0x230bcf 0xf5e37 0xf6418 0xf6648 0xf6882 0x102235 0x3013d2 0xff4f3 0xff777 0xff7b7 0x46afe2 0x45cad9 0x45cb54 0xc4899 0xc4b3d 0xacbe83 0x1c4f376 0x1c4ee06 0x1c36a82 0x1c35f44 0x1c35e1b 0x1bea7e3 0x1bea668 0x1565c 0x204d 0x1f75 0x1)
libc++abi.dylib: terminate called throwing an exception
It seems like textview has a detail label and maybe I needed an outlet for that?
Change this line :
saintsDetailViewController *detailViewController = [segue destinationViewController];
To this:
saintsDetailViewController *detailViewController = (saintsDetailViewController *)[segue destinationViewController];
EDIT:
You should show your view first and then call the configureView method. Try to call configureView in viewDidLoad of that ViewController.
- (void)setStory:(saintsNewsStory *)newStory
{
// if (_story != newStory) {
_story = newStory;
// Update the view.
// [self configureView];
// }
}
-(void) viewDidLoad {
[self configureView];
}
- (void)configureView
{
self.storyText.text = self.story.storyText; //the app gets to here but does not show the view. why?
}
Test my theory. Put NSLog(#"%d", [self.yourTableView indexPathForSelectedRow].row); in your current prepareForSegue method immediately before or after the line you currently use the indexPathForSelectedRow, and see what it is returning in your console. Probably 0.
Instead of connecting the segue from the UITableViewCell Prototype to the DetailViewController, connect it from the UITableViewController to the DetailViewController and then use the methods below:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
SaintsDetailViewController *detailViewController = [[SaintsDetailViewController alloc] init];
saintsNewsStory* displayThisStory =[self.dataController objectAtIndex:indexPath.row];
[detailViewController setStory:displayThisStory];
[self performSegueWithIdentifier:#"showDetail" sender:indexPath];
}
and your prepareForSegue: method:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
if ([[segue identifier] isEqualToString:#"showDetail"]) {
saintsDetailViewController *detailViewController = [segue destinationViewController];
}
}

Resources