I have a project where I show directions between current location and another location on a map (MapKit)
All works well. And I can get alternative routes.
request.requestsAlternateRoutes = YES;
But when the user tap on a route I show an annotation with distance and some other info. I want to pass this spesific route to another view. How can I achieve that? Like the original Map app on iOS. I can get different routes, and tap on a route to get direction details.
I have googled a lot, and the closest example is this:
[directions calculateDirectionsWithCompletionHandler:^(MKDirectionsResponse *response, NSError *error) {
// Now handle the result
if (error) {
NSLog(#"There was an error getting your directions");
return;
}
_currentRoute = [response.routes firstObject];
But _currentRoute is the first one. I want to let the user select currentRoute on tap on the map.
I figured it out. Not so elegant, but it does the job.
Make an ivar - NSMutableArray *routeArray in the header file.
I also have a MKMapView outlet in my header.
In my getDirections method, I tagged the MKMapView instance with a counter and added the route object to the routeArray.
When the delegate for making the Annotation is run, I can tag the anntotationView with the mapView.tag.
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation
When I tap on the annotation:
(void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control
I pass the view.tag to my segue.
NSLog(#"Counter tag: %ld", (long)view.tag);
[self performSegueWithIdentifier:#"detaljer" sender:view];
I can then pass my selected route to my new view.
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
WeatherTableViewController *routeController =
[segue destinationViewController];
long row = [sender tag];
MKRoute *route = routeArray[row];
routeController.selectedRoute = route;
}
Related
I am a beginner with XCode and I am trying to find a way for the User to input information inside a callbox of a pin they had placed on the map.
So if the User drops a pin and they tap on the pin, I want them to be able to type in a title and a subtitle of that location.
So far, I have created a map using MKMapView and I have implemented UILongPressGestureRecognizer and a void method that fires the moment a pin is created.
I am not sure what to do next? Thank you for your help.
Edit1: I don't have the required reputation to post an image, so here is a link of what I am trying to accomplish: http://imgur.com/gixFxpI
If it is possible to type in the title and subtitle without the use of a disclosure button, that would be swell. Later on I will develop the disclosure button.
Edit2: I have three new questions: 1. Why isn't my disclosure button appearing in my callout? 2. What is reuseidentifier for? Here is what I have so far.
I have embedded a navigation controller to my initial view controller that has the MKMapView in it. I created a second View Controller and I connected the MKMapView to the second View Controller and made my identifier for the second View Controller as 'heylisten' (good old Zelda).
My third question is in the prepareForSegue method, what information needs to go after the dot? destinationViewController.???? = sender.annotation;
// This is your callout box
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation
{
MKAnnotationView *annotationView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"overhere"];
annotationView.canShowCallout = YES;
UIButton *rightDisclosureButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
annotationView.rightCalloutAccessoryView = rightDisclosureButton;
return annotationView;
}
// This is a segue when you tap the right disclosure button
- (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control
{
[self performSegueWithIdentifier:#"heylisten" sender:view];
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(MKAnnotationView *)sender
{
if ([segue.identifier isEqualToString:#"heylisten"])
{
ViewController *destinationViewController = segue.destinationViewController;
// grab the annotation from the sender
destinationViewController = sender.annotation;
}
}
If you're targeting iOS 8, use a UIAlertController:
https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIAlertController_class/
If you're targeting iOS 7 or earlier, use a UIAlertView:
https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIAlertView_Class/index.html#//apple_ref/occ/cl/UIAlertView
Both of these types have mechanisms to add an input text field and specify a callback to be executed with the text the user typed.
I'm using MapKit and I have 2 callout accessories in my pins.
I'm trying to implement a button for updating the pin's title and one for deleting the pin.
Right now, anytime I press a button on the annotation, it only deletes the pin.
How do I get it to respond differently for the right button vs the left button?
-(void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control {
id <MKAnnotation> annotation = [view annotation];
if ([annotation isKindOfClass:[MKPointAnnotation class]])
{
NSLog(#"Clicked");
if(view.rightCalloutAccessoryView){
[self.mapView removeAnnotation:annotation];
}
else{
float lat= annotation.coordinate.latitude;
float longitude = annotation.coordinate.longitude;
[self.mapView removeAnnotation:annotation];
MKPointAnnotation *pointAnnotation = [[MKPointAnnotation alloc] init];
pointAnnotation.title = _titleOut.text;
pointAnnotation.subtitle = _subtitle.text;
pointAnnotation.coordinate = CLLocationCoordinate2DMake(lat, longitude);
[self.mapView addAnnotation:pointAnnotation];
}
}
}
This line:
if(view.rightCalloutAccessoryView){
says essentially "if view.rightCalloutAccessoryView is not nil".
Since you are setting the right-side accessory on all annotation views, that if condition will always be true and so tapping either accessory will execute the code inside that if which is to remove the annotation.
Instead, you want to check what button or control was tapped in this specific case of the delegate method being called (not whether the view has a right-side accessory defined).
Fortunately, the delegate method passes exactly what control was tapped in the control parameter.
The control parameter can be directly compared against the view's right/left accessory view to tell which was tapped:
if (control == view.rightCalloutAccessoryView) {
Some unrelated points:
The latitude and longitude properties in annotations are of type CLLocationDegrees (aka double) which has higher precision than float so to avoid losing accuracy, use CLLocationDegrees or double:
CLLocationDegrees lat= annotation.coordinate.latitude;
The MKPointAnnotation allows you to change the title directly (it's not read-only like the default id<MKAnnotation>) so you don't need to remove and create a new annotation. It simplifies the code a bit:
-(void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control {
if ([view.annotation isKindOfClass:[MKPointAnnotation class]])
{
NSLog(#"Clicked");
if (control == view.rightCalloutAccessoryView) {
[self.mapView removeAnnotation:view.annotation];
}
else {
// Cast view.annotation as an MKPointAnnotation
// (which we know it is) so that compiler sees
// title is read-write instead of the
// default id<MKAnnotation> which is read-only.
MKPointAnnotation *pa = (MKPointAnnotation *)view.annotation;
pa.title = _titleOut.text;
pa.subtitle = _subtitle.text;
//If you want callout to be closed automatically after
//title is changed, uncomment the line below:
//[mapView deselectAnnotation:pa animated:YES];
}
}
}
i want to get annotation title & subtitle from this method.
what the way go get that.
-(void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view{
//NSString * title = [annotations title];
}
The view has an annotation property that you can use which will return the annotation you originally used when creating the view.
I've read most of the other questions here, as well as read a lot, but I'm not finding an answer.
I'm using this code to create the buttons
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation{
NSLog(#"called the MKAnnotView method, tag is: %i", self.tag);
static NSString *s = #"ann";
MKAnnotationView *pin = [mapView dequeueReusableAnnotationViewWithIdentifier:s];
if (!pin) {
pin = [[MKPinAnnotationView alloc]initWithAnnotation:annotation reuseIdentifier:s];
pin.canShowCallout = YES;
pin.calloutOffset = CGPointMake(0, 0);
UIButton *button = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
button.tag=self.tag++;
[button addTarget:self
action:#selector(viewDetails:)
forControlEvents:UIControlEventTouchUpInside];
pin.rightCalloutAccessoryView = button;
}
return pin;
}
-(void) viewDetails: (id) sender {
UIButton *button = sender;
NSLog(#"viewDetails called with button.tag: %i",button.tag);
//[self performSegueWithIdentifier:#"detailView" sender:self];
}
I'm creating a variable amount of pins, in a specific order. When I create them, I get the expected output of "called the MKAnnotView method, tag is 0" then 1, 2, 3, etc.
The annotations are in a very specific order. Zeroth, first, second, so forth. I expect the buttons I created for each annotation to have a tag that fits their index, so that when I segue to the detailViewController, I know which item on the list needs to be pulled and the view populated.
That's not happening. The buttons are each getting randomly assigned tags that repeat. My last case had two tags with 3, and two with zero.
I cannot for the life of me understand why.
Any hints?
Based on comments to my question, the solution I came up with was implementing the following
- (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control
{
self.tag = [self.annotArray indexOfObject:view.annotation];
NSLog(#"calloutAccessoryControlTapped: annotation = %i", self.tag);
[self performSegueWithIdentifier:#"ShowDetails" sender:self];
}
Whenever I create an annotation, I was already adding it to an annotArray property of this viewController for another use (as the view's built-in annotation array was not populating in a predictable manner)
my prepareForSegue method pulls self.tag and updates a property of the destinationViewController.
My app places a pushpin on the map and then selects its using animation so the user has a visual clue and can immediately read the title/subtitle. The following code works in both iOS4 and iOS5, but in iOS5, the annotation doesn't get selected automatically unless I change the animation to NO in the selectAnnotation method.
Any ideas why?
MapAnnotations *pushpin = [[MapAnnotations alloc] initWithCoordinate:coordinate];
pushpin.title = [selectedStation valueForKey:#"name"];
pushpin.subtitle = [selectedStation valueForKey:#"address"];
[stationMap addAnnotation:pushpin];
[stationMap selectAnnotation:pushpin animated:YES];
[pushpin release]; pushpin = nil;
Not sure why it would work before but the animation probably requires the annotation view to be created and ready which is unlikely immediately after adding the annotation.
What you can do is move the selection to the didAddAnnotationViews delegate method which should work on all iOS versions:
- (void)mapView:(MKMapView *)mapView didAddAnnotationViews:(NSArray *)views
{
for (MKAnnotationView *av in views) {
if ([av.annotation isKindOfClass:[MapAnnotations class]]) {
MapAnnotations *pushpin = (MapAnnotations *)av.annotation;
if (_this_pushpin_is_the_one_to_select) {
[mapView selectAnnotation:av.annotation animated:YES];
break; //or return;
}
}
}
}