I have populated a couple of locations on MKMapView using MKPointAnnotation(s). On tapping annotations I am showing some options with UIActionSheet menu. The options have some delete functionality which would delete the selected annotation on map when user taps the delete option on UIActionSheet. The issue is that I am not able to determine which annotation point is clicked, I seem to have no reference to it.
The code that adds annotation point is:
while(looping array of locations)
{
MKPointAnnotation *annotationPoint = [[MKPointAnnotation alloc] init];
annotationPoint.coordinate = {coord of my location}
annotationPoint.title = [anObject objectForKey:#"castTitle"];
annotationPoint.subtitle = [anObject objectForKey:#"storeName"];
[self.mainMapView addAnnotation:annotationPoint];
}
The code to show UIActionSheet on tapping annotation is:
-(MKAnnotationView*)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation{
if([annotation isKindOfClass:[MKUserLocation class]])
return nil;
static NSString* AnnotationIdentifier = #"AnnotationIdentifier";
MKPinAnnotationView *pinView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:AnnotationIdentifier];
pinView.animatesDrop = YES;
pinView.canShowCallout = YES;
pinView.pinColor = [self getAnnotationColor];
UIButton *rightButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
[rightButton setTitle:annotation.title forState:UIControlStateNormal];
[rightButton addTarget:self action:#selector(showOptions:) forControlEvents:UIControlEventTouchUpInside];
pinView.rightCalloutAccessoryView = rightButton;
return pinView;
}
-(IBAction)showOptions:(id)sender
{
UIActionSheet *sheet = [[UIActionSheet alloc] initWithTitle:NSLocalizedString(#"", #"") delegate:self cancelButtonTitle:NSLocalizedString(#"Cancel", #"Cancel") destructiveButtonTitle:nil otherButtonTitles:NSLocalizedString(#"Delete", #"Delete"), nil];
[sheet showInView:[self.view window]];
}
You can also Create a class which inherits from MKPointAnnotation, and add an id property , and use your class :
#interface MyPointAnnotation : MKPointAnnotation
#property int pointId;
#end
and then use it in your view controller:
create annotation:
MyPointAnnotation *myAnnotation = [[MyPointAnnotation alloc]init];
myAnnotation.coordinate= someCoordinates;
[myAnnotation setTitle:#"i am annotation with id"];
myAnnotation.pointId = 1;
[self.mapView addAnnotation:myAnnotation];
if you have an array of coordinates you can loop and create annotations.
and you can even customize annotation view by its id:
-(MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation{
MKPinAnnotationView *view=(MKPinAnnotationView*)[self.mapView dequeueReusableAnnotationViewWithIdentifier:#"reuseme"];
if (!view) {
view=[[MKPinAnnotationView alloc]initWithAnnotation:annotation reuseIdentifier:#"reuseme"];
}
if (((MyPointAnnotation *)annotation).pointId == 1)
{
//the annotation with id 1.
}
return view;
}
Seems like there are two approaches.
If only one annotation can be selected at a time, you could access the -selectedAnnotations property of the enclosing MKMapView.
Another approach is to inspect sender in showOptions:, which is a reference to the UIButton that triggered the action. Find out its enclosing MKAnnotationView, which will give you the associated -annotation. You could then either stash this as an ivar or (my preferred approach) use some runtime magic - in the form of objc_setAssociatedObject(), declared in <objc/runtime.h> - to attach a reference to the annotation to the action sheet, allowing easy retrieval in its delegate callback.
(You could actually do this back in the button creation phase if you wanted, and attach a reference to the annotation to the UIButton, which can be picked up directly in showOptions: and reattached to the action sheet.
But [MKMapView selectedAnnotations] I would think is the easier way to go if it suits your needs.
Related
I have an MKMapView that I add pins to. They load correctly with their relevant graphics but if I zoom in and zoom back out they loose their graphic and turn into a standard red pin with the only customisation being the pin name (even my disclosure indicator disappear).
So far to try and fix it I've tried:
Tried png’s, checked on faster device, Changed everything from MKPinAnnotation to MKAnnotation, returning to a normal MKAnnotation instead of my custom CBAnnotation, Various sample codes for loading custom pins, Lowered quality of map overlay in case it was a loading issue but still an issue.
- (void)addPins {
mapPinsArray = [[NSMutableArray alloc] init];
for (MapPoint *mappoint in mapPointsArray) {
CBAnnotation *annotation = [[CBAnnotation alloc] init];
annotation.coordinate = CLLocationCoordinate2DMake(mappoint.loclat, mappoint.loclong);
annotation.title = mappoint.stopAreaName;
annotation.mapPoint = mappoint;
[mapPinsArray addObject:annotation];
[self.myMapView addAnnotation:annotation];
}
}
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(CBAnnotation *)annotation {
if ([annotation isKindOfClass:[MKUserLocation class]]) {
//do nothing
return nil;
} else {
MKAnnotationView *annotationView = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"trailPoint"];
annotationView.canShowCallout = YES;
ButtonWithData *accessoryViewButton = [[ButtonWithData alloc] initWithFrame:CGRectMake(0, 0, 30, 30)];
[accessoryViewButton setBackgroundImage:[UIImage imageNamed:#"right_arrow"] forState:UIControlStateNormal];
accessoryViewButton.buttonData = annotation.mapPoint;
[accessoryViewButton addTarget:self action:#selector(disclosureButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
annotationView.rightCalloutAccessoryView = accessoryViewButton;
if (![[NSUserDefaults standardUserDefaults] boolForKey:annotation.mapPoint.stopAnimalName]) {
annotationView.image = [annotation.mapPoint lockedPinImage];
} else {
annotationView.image = [annotation.mapPoint unlockedPinImage];
}
return annotationView;
}
}
Fixed my own problem. I subclassed MKMapView to GenericMapView so that my code was cleaner (removing showsUserLocation, zoom/scroll/rotateEnabled, showsCompass etc. from the actual View Controller) but this meant the delegate wasn't setting correctly and viewForAnnotation wasn't being called on the pins reload.
I am trying to change my pin colour to purple, when I do it I lose the title though. Code is:
- (void)viewDidLoad {
[super viewDidLoad];
self.navigationController.navigationBarHidden=YES;
//init the location manager
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest;
[self.locationManager requestWhenInUseAuthorization];
self.mapView.showsUserLocation=YES;
self.userGeoPoint=self.message[#"userLocation"];
self.pinView = [[MKPointAnnotation alloc] init];
self.pinView.title=self.message[#"fromUser"];
self.coord = CLLocationCoordinate2DMake(self.userGeoPoint.latitude, self.userGeoPoint.longitude);
self.pinView.coordinate=self.coord;
//use a slight delay for more dramtic zooming
[self performSelector:#selector(addPin) withObject:nil afterDelay:0.5];
}
-(void)addPin{
[self.mapView addAnnotation:self.pinView];
[self.mapView selectAnnotation:self.pinView animated:YES];
MKCoordinateRegion region = MKCoordinateRegionMakeWithDistance(self.coord, 800, 800);
[self.mapView setRegion:[self.mapView regionThatFits:region] animated:YES];
}
-(MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotationPoint
{
if ([annotationPoint isKindOfClass:[MKUserLocation class]])//keep the user as default
return nil;
static NSString *annotationIdentifier = #"annotationIdentifier";
MKPinAnnotationView *pinView = [[MKPinAnnotationView alloc]initWithAnnotation:annotationPoint reuseIdentifier:annotationIdentifier];
pinView.pinColor = MKPinAnnotationColorPurple;
//now we can throw an image in there
return pinView;
}
I tried setting the title property for MKPinAnnotation but there isn't one. Is there anyway I can get around this?
In viewForAnnotation, you need to set canShowCallout to YES (it's NO by default):
pinView.pinColor = MKPinAnnotationColorPurple;
pinView.canShowCallout = YES;
A couple of unrelated points:
It looks like you have a property named pinView of type MKPointAnnotation that you are using to create the annotation object in viewDidLoad. Then in viewForAnnotation, you have a local variable also named pinView of type MKPinAnnotationView. Although there is no naming conflict here, it causes and implies some conceptual confusion. MKPointAnnotation is an annotation model class while MKPinAnnotationView is an annotation view class -- they are completely different things. It would be better to name the annotation property pin or userAnnotation for example.
This comment in viewForAnnotation:
//now we can throw an image in there
seems to imply that you could set a custom image in the view at this point. Setting a custom image in a MKPinAnnotationView is not recommended since that class is designed to display default pin images in one of three colors. To use a custom image, create a plain MKAnnotationView instead.
The pinColor property was deprecated in iOS 9. From iOS 9 onwards you should use pinTintColor which takes a UIColor rather than a MKPinAnnotationColor.
Old:
MKPinAnnotationView *view = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:nil];
view.pinColor = MKPinAnnotationColorPurple;
New:
MKPinAnnotationView *view = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:nil];
view.pinTintColor = [UI Color purpleColor];
There is more information available in the Apple docs.
I just upgrade to ios 10, apple changed the api.
pinColor no longer in use, instead, use tintColor
New:
MKPinAnnotationView *view = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:nil];
view.tintColor = [UIColor purpleColor];
Note that the pin title will be displayed when the user taps it:
If the value of this property is true, a standard callout bubble is shown when the user taps a selected annotation view. The callout uses the title and subtitle text from the associated annotation object.
( https://developer.apple.com/documentation/mapkit/mkannotationview/1452451-canshowcallout )
I am trying to show annotations on a mapView. All annotations come from JSON objects. They are divided into three groups. The user can select which annotations should be shown selecting an option on an segmentedIndex control.
As for now, the app is working as expected, the user selects an option from the segmentedIndex control, and the annotations are shown on the mapView.
My current issue is that I need the user to click on the callout view to open another viewController.
I think my code is right, but I guess it isn't then the showed callout view is the default calloutview, with title and subtitle. No action is fired when clicked on it.
Any help is welcome.
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation {
static NSString *identifier = #"MyLocation";
if ([annotation isKindOfClass:[PlaceMark class]]) {
MKPinAnnotationView *annotationView =
(MKPinAnnotationView *)[myMapView dequeueReusableAnnotationViewWithIdentifier:identifier];
if (annotationView == nil) {
annotationView = [[MKPinAnnotationView alloc]
initWithAnnotation:annotation
reuseIdentifier:identifier];
} else {
annotationView.annotation = annotation;
}
annotationView.enabled = YES;
annotationView.canShowCallout = YES;
// Create a UIButton object to add on the
UIButton *rightButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
[rightButton setTitle:annotation.title forState:UIControlStateNormal];
[annotationView setRightCalloutAccessoryView:rightButton];
UIButton *leftButton = [UIButton buttonWithType:UIButtonTypeInfoLight];
[leftButton setTitle:annotation.title forState:UIControlStateNormal];
[annotationView setLeftCalloutAccessoryView:leftButton];
return annotationView;
}
return nil;
}
- (void)mapView:(MKMapView *)mapView
annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control {
if ([(UIButton*)control buttonType] == UIButtonTypeDetailDisclosure){
// Do your thing when the detailDisclosureButton is touched
UIViewController *mapDetailViewController = [[UIViewController alloc] init];
[[self navigationController] pushViewController:mapDetailViewController animated:YES];
} else if([(UIButton*)control buttonType] == UIButtonTypeInfoDark) {
// Do your thing when the infoDarkButton is touched
NSLog(#"infoDarkButton for longitude: %f and latitude: %f",
[(PlaceMark*)[view annotation] coordinate].longitude,
[(PlaceMark*)[view annotation] coordinate].latitude);
}
}
Most likely the map view's delegate is not set in which case it won't call viewForAnnotation and will instead create a default view (red pin with a callout showing only the title and subtitle -- no buttons).
The declaration in the header file does not set the map view's delegate. That just tells the compiler that this class intends to implement certain delegate methods.
In the xib/storyboard, right-click on the map view and connect the delegate outlet to the view controller or, in viewDidLoad, put mapView.delegate = self;.
Unrelated, but I want to point out that in calloutAccessoryControlTapped, rather than checking the buttonType, you probably want to just know whether it's the right or left button so just do:
if (control == view.rightCalloutAccessoryView) ...
See https://stackoverflow.com/a/9113611/467105 for a complete example.
There are at least two problems with checking the buttonType:
What if you want to use the same type for both buttons (eg. Custom)?
In iOS 7, setting a button to UIButtonTypeDetailDisclosure ends up actually creating a button of type Info (see MKAnnotationView always shows infoButton instead of detailDisclosure btn for details). So the check for buttonType UIButtonTypeDetailDisclosure would fail (on iOS 7).
I have a map that has multiple annotations that are plotted on the map sourced from JSON data that are broken up by category within a UITableViewController that contains information like their latitude and longitude as well as other information like: address, phone number and so on...
I know how to add a detail disclosure button to the annotation view for each one, but how to do I get that information into a detail viewController?
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation
{
if([annotation isKindOfClass:[MKUserLocation class]]) {
return nil;
}
MKPinAnnotationView *pinView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"annoPin"];
MKAnnotationView *view = [self.mapView dequeueReusableAnnotationViewWithIdentifier:#"annoView"];
if(!view) {
view = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"annoView"];
}
UIButton *rightButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
[rightButton addTarget:nil action:#selector(showDetails :) forControlEvents:UIControlEventTouchUpInside];
pinView.animatesDrop = YES;
view.rightCalloutAccessoryView = rightButton;
view.image = [UIImage imageNamed:#"check.png"];
view.enabled = YES;
view.canShowCallout = YES;
return view;
}
As you can see I've created a button, but how do I get the JSON info for each category over to a detail ViewController for each category inside of the UITableViewController?
When you create the annotation that you add to your mapview you need to give it all the information that will eventually get to the detail view controller, or at least a reference to the data that you can get later. Then in mapView:annotationView:calloutAccessoryControlTapped:, which gets called on the map delegate when a disclosure button is tapped, you get the annotationview's annotation and get the data from there.
I have a map view with pins annotations into it. I need to pass to the detail view the coordinates of a pin when the user press the disclosure button of the pin information. How can I get the coordinates of the pin when I am into the showDetails method? I am using the next code.
- (void)showDetails:(id)sender {
DetailViewController *detailViewController = [[DetailViewController alloc] initWithNibName:#"DetailViewController" bundle:nil ];
// HERE I NEED TO PASS THE COORDINATES OF THE PIN TO THE DETAILVIEWCONTROLLER.
[self.navigationController pushViewController:detailViewController animated:YES];
[detailViewController release];
}
- (MKAnnotationView *)mapView:(MKMapView *)theMapView viewForAnnotation:(id <MKAnnotation>)annotation {
// If it's the user location, just return nil.
if ([annotation isKindOfClass:[MKUserLocation class]])
return nil;
else { // Handles the other annotations.
// Try to dequeue an existing pin view first.
static NSString *AnnotationIdentifier = #"AnnotationIdentifier";
MKPinAnnotationView *pinView = (MKPinAnnotationView *)[self.mapView dequeueReusableAnnotationViewWithIdentifier:AnnotationIdentifier];
if (!pinView) {
// If an existing pin view was not available, creates one.
MKPinAnnotationView *customPinView = [[[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:AnnotationIdentifier] autorelease];
customPinView.animatesDrop = YES;
customPinView.canShowCallout = YES;
// Adds a detail disclosure button.
UIButton *rightButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
[rightButton addTarget:self action:#selector(showDetails:) forControlEvents:UIControlEventTouchUpInside];
customPinView.rightCalloutAccessoryView = rightButton;
return customPinView;
} else
pinView.annotation = annotation;
}
return nil;
}
Thanks for reading.
I cannot give you a working code solution right now since i don't have my mac here. But I can tell you about a possible approach.
Create a method in DetailViewController that populates its attributes, so you can call it from showDetails, before you push it. So the sequence of events would be like this.
Click on disclosure button -> showDetails called -> DetailViewController created and populated -> push DetailViewController
Hope this helped you
You can your UIButton button custom like this :
#interface CustomButton : UIButton {
CLLocationCoordinate2D pinCoordinate;
}
Now in your viewForAnnotation :
// Adds a detail disclosure button.
CustomButton *rightButton = [CustomButton buttonWithType:UIButtonTypeDetailDisclosure];
rightButton.pinCoordinate = annotation.coordinate
[rightButton addTarget:self action:#selector(showDetails:) forControlEvents:UIControlEventTouchUpInside];
customPinView.rightCalloutAccessoryView = rightButton;
And finally to get the coordinate in the showDetails method :
CLLocationCoordinate2D currentCoord = ((CustomButton*)sender).pinCoordinate;