Segue from callout in MKMapView - objective-c

Hi I have a mapView that has annotations pop up, I want to be able to segue when the annotation callout button is clicked. I have some problems though when I do it. I have a few questions
1) Do I have to embed the mapViewController in a navigation Controller? If yes, my annotations do not show up when I do, how come?
2) does prepareforsegue get called from performSegueWithIdentifier?
3) when u send self, in this case what would self be?
Thanks
- (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control
{
[self performSegueWithIdentifier:#"Present Photo" sender:self];
}
Realized the problem it occurs here, I used to get a map controller from the id detail but now I think its a navigation controller, how do I get reference to the map controller now?
-(void) updateSplitViewDetail{
// ERROR OCCURS HERE!!! No longer map controller since I embed in navigation controller
id detail = [self.splitViewController.viewControllers lastObject];
if ([detail isKindOfClass:[MapViewController class]]) {
MapViewController *mapVC = (MapViewController*) detail;
mapVC.delegate = self;
mapVC.annotations = [self mapAnnotations];
}
}
- (void)viewDidLoad
{
[super viewDidLoad];
[self updateSplitViewDetail]; //Error may be here
}

1)
Yes. If you want to perform a push segue, the source view controller (your map view controller) should be embedded in in navigation controller.
I'm not sure why your annotations/callouts aren't appearing in that case -- I've seen plenty of projects that work correctly that way. Perhaps your reference to the map view when you add the annotations isn't what you think it is? (And you're adding annotations to nil instead?) You'll need to provide more details for us to help. (Edit your question or post a new question since it's sort of a separate issue.)
2)
Yes. prepareForSegue:sender: is called after you call performSegueWithIdentifier:sender:.
3)
The "sender" argument in these methods is entirely for your own use -- its sole reason for existence is to allow you to pass some context from the code that calls performSegueWithIdentifier:sender: to the implementation of prepareForSegue:sender:. (Or in the case of segues automatically performed when the user taps some control, to allow your prepareForSegue:sender: implementation to know which control was tapped.)
So, pass whatever you want: self is fine, and so is nil if you're not making use of it. Or if it's useful for your prepareForSegue:sender implementation to know which callout was tapped, you might consider passing the annotation view's annotation as "sender" (say, so it can set up the destination view controller with appropriate info).

Related

Pass variable to view controller before viewDidLoad

I perform a segue which is defined in the Storyboard to open a new view controller. I need to configure the destination view controller of the segue in a special state where some of it's buttons does not needed to be displayed.
I know that I can do this by setting a variable on this controller in my source view controller's -prepareForSegue:sender:. The problem with this is, that firstly it instantiates the controller, so it's -viewDidLoad: will run, then only after can I set anything on it.
I can't create the controller entirely from code, because it's user interface is in Storyboard. -instantiateViewControllerWithIdentifier: also calls -viewDidLoad first obviously.
I could probably use a semaphore and add the initialization code into my destination controller's -viewWillAppear, but that's ugly, it has to be some more elegant way to do this than doing a check every time the view appears. (My settings need to be done only once.)
Is there some way to pass variables into the controller before it's -viewDidLoad runs?
EDIT: It looks like this happens only if I trigger the segue from code using -performSegueWithIdentifier:.
On my machine and on iOS 8.0 and iOS 9.0, viewDidLoad is called after prepareForSegue. So, something like the following worked for my test case of your answer.
In your source controller:
- (void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
TimViewController * controller = segue.destinationViewController;
if( [controller isKindOfClass:[TimViewController class]] )
controller.name = #"tim";
}
In your destination controller (TimViewController):
- (void)viewDidLoad
{
[super viewDidLoad];
// Do view setup here.
NSLog( #"view did load %#", self.name );
}
Add a segue (a show segue) from your source control to the destination view controller.
Output:
2015-09-17 19:09:04.351 Test[51471:7984717] view did load tim
I think there is some confusion here. -prepareForSegue:sender: gets called before -viewDidLoad gets called. Please double check your implementation.
Edit:
May be this thread will help you understand this and one of the mentioned cases fall in your case.

Push view to navigation controller from button in other class

I have a UIButton that I create in my sub class ViewController, and add it to my MainViewController.
Now, I added a target method to this button that should push another view controller to my Navigation controller (the one that in the MainViewController).
I know that the method did call when I push the button, but the view wasn't push to the Navigation Controller.
I scanned this drawing - this is the drawing (I also added part of my code):
This is the code I'm using in my button:
(remember it's in a deferent ViewController).
- (void)buttonPressed:(UIButton *)sender
{
Photo_ScreenGlobalView *photo = [[Photo_ScreenGlobalView alloc] init];
[self.navigationController pushViewController:photo animated:YES];
}
Usually I solve these situations with delegation. If there is a view controller which is subordinate to another (i.e. a "sub" view controller) but should have the ability to trigger navigation changes, etc... then it should have a weak pointer back to it's 'parent'. Then the parent VC should implement an appropriately named protocol with a callback for the child to use. The names of these things can be generic, such as #property navigationDelegate and requestNavigationToViewController: or they can be more semantic, such as #property userFormDelegate and userFormDoneButtonPressed:
Generally speaking, a subordinate view controller should not be able to directly modify navigation at it's parent's level; but it can trigger it via more loosely-coupoled interfaces like these.
i came back to let you all know how i actually did it.
after googling a lot found this nice and quick guide how to make DELEGATE
and working with delegate solved all my problems. if you need any help don't hesitate to send me PM.
this is the guide:
http://css.dzone.com/articles/do-not-publishcreating-your

how to fire mapView:didSelectAnnotationView

I'm new to iPhone development. I've been reading several questions on how to make a google maps annotation callout window accept line breaks. Every tutorial I've read requires me to fire the mapView:didSelectAnnotationView method. But I have no idea how to trigger this. things I've tried include
putting the method in my MapViewController.m file which extends UIViewController
putting the method in a MapView.m file which extends MKMapView, then have my Mapview element in my storyboard reference it as the class to use
There's so much about xcode, objective c, and iphone development that I don't understand, so i can't tell where my problem lies.
At the moment, my map does plot my desired marker on the desired location. I just need to understand how to fire the mapView:didSelectAnnotationView and mapView:viewForAnnotation functions before I can start customizing the call out box.
Does anyone have step by step instructions on how to trigger these functions?
A bit of background
A few things to note:
You don't call mapView:didSelectAnnotationView. The MKMapView calls that function on it's delegate. In other words, when you set up an MKMapView, you tell it: "hey, listen, anytimme you need to tell me what's happening on the map, go tell this guy, he'll handle them for you". That "guy" is the delegate object, and it needs to implement mapView:didSelectAnnotationView (that's also why its name "did select", ie, it already happened, as opposed to "select"). For a simple case, the delegate is often the UIViewController that owns the MKMapView, which is what I'll describe below.
That method will then get triggered when the user taps on one of your annotations. So that's a great spot to start customizing what should happen when they tap on an annotation view (updating a selection, for instance).
It's not, however, what you want if you want to customize what annotation to show, which is what it sounds like you're actually after. For that, there's a different method just a few paragraphs earlier on the same man page: mapView:viewForAnnotation. So substitute this method if you find that mapView:didSelectAnnotationView isn't what you were looking for.
What you can do
If you got as far as a map with a marker, I'm guessing you have at least:
* a view controller (extendeding from UIViewController, and
* an MKMapView that you've added to the view for that view controller, say named mapView
The method you want to fire is defined as part of the MKMapViewDelegate protocol.
The easiest way to get this wired is to:
make your UIViewController the delegate for you MKMapView
in code, say in your viewDidLoad, of your MapViewController.m you could do mapview.delegate = self, OR
in Interface Builder, you could drag the connection from the the MKMapView delegate property to the file's owner
then, define a method on your UIViewController called mapView:didSelectAnnotationView, declaring it just like the protocol does, in your MapViewController.m file:
- (void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view {
// whatever you need to do to your annotation and/or map
}
Good luck!
mapView:didSelectAnnotationView is a delegate method of the map view, you can read about it here:
MKMapViewDelegate Protocol Reference
You don't need to call it, the map view will call it "by it self" and send it to every view/view controller that registered as it's delegate.
What do you need to do
Basically you need to add the MKMapViewDelegate on your .h file, what will look something like this:
#interface someViewController : UIViewController <MKMapViewDelegate>
Then in the .m file, after you instantiate the map view you should add:
mapView.delegate = self;//were self refers to your controller
From this point and on your controller will be able to "receive messages" from the map view which are the methods that you can see on the MKMapViewDelegate reference I linked to.
So to implement the mapView:didSelectAnnotationView you need to add in your .m file
- (void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view{
//if you did all the steps this methosd will be called when a user taps the annotation on the map.
}
What is happening
What happens in the background is:
The map view has a method (Apple codded) that handles the AnnotationView touch events.
When a touch event take place it sends a "message" to all of it's delegates saying "Hey a user did Select Annotation View on this map, Do with it what ever you need".
usually it looks like that:
[self.delegate mapView:someMapView didSelectAnnotationView:someAnnotationView];
Then every view/controller that assigned itself as a delegate and implemented the method will cal this method.
Good luck
Place *place = [[Place alloc] init];
PlaceMark *placeMark = [[PlaceMark alloc] initWithPlace:place];
[self.mapView selectAnnotation:placeMark animated:YES];

How to get a reference to the view controller of a superview?

Is there a way to get a reference to the view controller of my superview?
There were several instances that I needed this on the past couple of months, but didn't know how to do it. I mean, if I have a custom button on a custom cell, and I wish to get a reference of the table view controller that controls the cell I`m currently in, is there a code snippet for that? Or is it something that I should just solve it by using better design patterns?
Thanks!
Your button should preferably not know about its superviews view controller.
However, if your button really needs to message objects that it shouldn't know the details about, you can use delegation to send the messages you want to the buttons delegate.
Create a MyButtonDelegate protocol and define the methods that everyone that conforms to that protocol need to implement (the callback). You can have optional methods as well.
Then add a property on the button #property (weak) id<MyButtonDelegate> so that any class of any kind can be set as the delegate as long as it conforms to your protocol.
Now the view controller can implement the MyButtonDelegate protocol and set itself as the delegate. The parts of the code that require knowledge about the view controller should be implemented in the delegate method (or methods).
The view can now send the protocol messages to its delegate (without knowing who or what it is) and the delegate can to the appropriate thing for that button. This way the same button could be reused because it doesn't depend on where it is used.
When I asked this question I was thinking of, in a situation where I have custom cells with buttons on them, how can the TableViewController know which cell's button was tapped.
More recently, reading the book "iOS Recipes", I got the solution:
-(IBAction)cellButtonTapped:(id)sender
{
NSLog(#"%s", __FUNCTION__);
UIButton *button = sender;
//Convert the tapped point to the tableView coordinate system
CGPoint correctedPoint = [button convertPoint:button.bounds.origin toView:self.tableView];
//Get the cell at that point
NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:correctedPoint];
NSLog(#"Button tapped in row %d", indexPath.row);
}
Another solution, a bit more fragile (though simpler) would be:
- (IBAction)cellButtonTapped:(id)sender
{
// Go get the enclosing cell manually
UITableViewCell *parentCell = [[sender superview] superview];
NSIndexPath *pathForButton = [self.tableView indexPathForCell:parentCell];
}
And the most reusable one would be to add this method to a category of UITableView
- (NSIndexPath *)prp_indexPathForRowContainingView:(UIView *)view
{
CGPoint correctedPoint = [view convertPoint:view.bounds.origin toView:self];
return [self indexPathForRowAtPoint:correctedPoint];
}
And then, on your UITableViewController class, just use this:
- (IBAction)cellButtonTapped:(id)sender
{
NSIndexPath *pathForButton = [self.tableView indexPathForRowContainingView:sender];
}
If you know which class is the superview of your view controller, you can just iterate through the subviews array and typecheck for your superclass.
eg.
UIView *view;
for(tempView in self.subviews) {
if([tempView isKindOfClass:[SuperViewController class] ])
{
// you got the reference, do waht you want
}
}

prepareForSegue is not called after performSegue:withIdentifier: with popover style

I have a universal app, where I am sharing the same controller for a IPad and IPhone storyboard.
I have put a UILongPressGestureRecognizer on a UITableView, that when a cell is pressed on iPhone it calls an action that perform a segue:
-(IBAction)showDetail:(id)sender {
UILongPressGestureRecognizer *gesture = (UILongPressGestureRecognizer*)sender;
if (gesture.state == UIGestureRecognizerStateBegan) {
CGPoint p = [gesture locationInView:self.theTableView];
NSIndexPath *indexPath = [self.theTableView indexPathForRowAtPoint:p];
if (indexPath != nil) {
[self performSegueWithIdentifier:SEGUE_DETAIL sender:indexPath];
}
}
}
the segue is a detail view performed as a 'push'. The first thing you should notice is that the sender is an NSIndexPath, is the only way I found for passing the selected cell. Maybe there's a better solution.
Everything works fine, in a sense that the segue is performed, and before the prepareForSegue is called too.
However it happens that on iPad, I have changed the segue identifier to Popover.
Now things are working in part, the segue is performed, but prepareForSegue is not called and therefore the destination view controller is not set up as it should be.
What am I doing wrong ?
What I have discovered so far, is that with any segue identifier that is not popover these are the invocations made by iOS:
prepareForSegue (on source controller)
viewDidLoad (on destination controller)
while in popover segue the invocation order is:
viewDidLoad (on destination controller)
prepareForSegue (on source controller)
just because I put all my logic in viewDidLoad, the controller was not properly initialized, and a crash happened. So this is not exactly true that prepareForSegue is not called, the truth is that I was getting an exception, and I wrongly mistaken as prepareForSegue not getting called.
I couldn't put everything in viewWillAppear because a call to CoreData had to be made and I didn't want to check if entities were ok each time the view display.
How did I solve this ? I created another method in destination controller
-(void)prepareViewController {
// initialization logic...
}
and changing the prepareForSegue method in source controller itself:
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
MyViewController *mvc = (MyViewController*)[segue destinationViewController];
// passing variable
// with segue style other than popover this called first than viewDidLoad
mvc.myProp1=#"prop1";
mvc.myProp2=#"prop2";
// viewWillAppear is not yet called
// so by sending message to controller
// the view is initialized
[mvc prepareViewController];
}
don't know if this is expected behavior with popover, anyway now things are working.
I've noticed that the boiler plate code for Xcode's Master-Detail template (iPhone) uses the following pattern for configuring the detail VC's view:
the detail VC's setters (for properties) are overwritten in order to invoke the configureView method (configureView would update all your controls in the view, e.g., labels, etc.)
the detail VC's viewDidLoad method also invokes the configureView method
I did not follow this pattern the other day when I was trying to re-use a detail VC in my movie app, and this gave me trouble.
I don't have much experience with popovers; however, if the pattern above is used with a detail VC that is displayed inside a popover, then wouldn't the detail VC's view get configured when you set the detail VC's properties from within the prepareForSegue method?