I have an MKMapview with a custom annotation image, when i click on the annotation i change the image for a bigger sized one and this causes the point to shift.
Is there a way to make sure the image gets aligned on the latitude and longitude again ?
- (void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view
{
MKPointAnnotation * pin = view.annotation;
if ([pin isKindOfClass:[MKUserLocation class]])
{
return;
}
NSMutableString * imageName = [[NSMutableString alloc] initWithString:[((SpotView *)self.baseView) generatePin:self.category]];
[imageName appendString:#"-selected"];
view.image = [UIImage imageNamed:imageName];
}
The results :
I have fixed my issue using the following property in the didSelectAnnotationView method
customPinView.centerOffset = CGPointMake(xOffset,yOffset);
and setting it back to zero in the didDeselectAnnotationView method
customPinView.centerOffset = CGPointMake(0, 0);
Related
I am currently struggling to use a custom map pin image instead of the default coloured pins. I have tried some guides online but can’t seem to integrate it into my current set up.
Currently I have a certain coloured (tinted) pin set during certain times (online) and when the time is outside these parameters the pin turns a different colour.
I want to change these pins to two different custom pin images that I have created; one for online and one for offline.
I have read something about the MKPinAnnotationView not liking the pinView.image property (I have tried this and can confirm it doesn’t work).
If someone could edit this code so I can have a “online.png“ image for the annotation tag 0 and an “offline.png” image for the annotation tag 1, that would be perfect.
MapViewController.m
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation {
MKPinAnnotationView *pinView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"CustomPinAnnotationView"];
UIButton *advertButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
pinView.rightCalloutAccessoryView = advertButton;
pinView.canShowCallout = YES;
pinView.draggable = NO;
pinView.highlighted = YES;
if (annotation == mapView.userLocation)
return nil;
if (([(Annotation*)annotation tag] == 0) && [[(Annotation*)annotation area] isEqualToString:#"Online"])
{
pinView.pinTintColor = [UIColor colorWithRed:0.92 green:0.21 blue:0.21 alpha:1];
pinView.alpha = 1;
[advertButton addTarget:self action:#selector(Online:) forControlEvents:UIControlEventTouchUpInside];
}
else if (([(Annotation*)annotation tag] == 1) && [[(Annotation*)annotation area] isEqualToString:#"Offline"])
{
pinView.pinTintColor = [UIColor colorWithRed:0.8 green:0.8 blue:0.8 alpha:0.5];
pinView.alpha = 1;
[advertButton addTarget:self action:#selector(Offline:) forControlEvents:UIControlEventTouchUpInside];
}
return pinView;
}
Any help would be greatly appreciated; I am not a coder by nature so go easy.
Cheers
Lewis
If you want to display a pin as annotation, you use MKPinAnnotationView. If you want to use a custom annotation image, you had to use MKAnnotationView, and set the image property.
So I suggest to replace
MKPinAnnotationView *pinView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"CustomPinAnnotationView"];
by
MKAnnotationView *pinView = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"CustomPinAnnotationView"];
and set the image property, e.g. using:
pinView.image = [UIImage imageNamed:#„xxx“];
Of course, you should rename pinView, CustomPinAnnotationView and xxx appropriately.
I'm trying to render MKPolygon using the following code:
NSMutableArray *overlays = [NSMutableArray array];
for (NSDictionary *state in states) {
NSArray *points = [state valueForKeyPath:#"point"];
NSInteger numberOfCoordinates = [points count];
CLLocationCoordinate2D *polygonPoints = malloc(numberOfCoordinates * sizeof(CLLocationCoordinate2D));
NSInteger index = 0;
for (NSDictionary *pointDict in points) {
polygonPoints[index] = CLLocationCoordinate2DMake([[pointDict valueForKeyPath:#"latitude"] floatValue], [[pointDict valueForKeyPath:#"longitude"] floatValue]);
index++;
}
MKPolygon *overlayPolygon = [MKPolygon polygonWithCoordinates:polygonPoints count:numberOfCoordinates];
overlayPolygon.title = [state valueForKey:#"name"];
[overlays addObject:overlayPolygon];
free(polygonPoints);
}
[self.stateMapView addOverlays:overlays];
I used the following code to provide stroke and fill colors:
- (MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:(id <MKOverlay>)overlay NS_AVAILABLE(10_9, 7_0);
{
if ([overlay isKindOfClass:[MKPolygon class]])
{
MKPolygonRenderer *pv = [[MKPolygonRenderer alloc] initWithPolygon:overlay];
pv.fillColor = [UIColor redColor];
pv.strokeColor = [UIColor blackColor];
return pv;
}
return nil;
}
Do I need to do something to render the Title? I think I should enable a configuration or something but I'm new to MapView. Or I need to create a UILabel?
Overlays don't automatically show their titles like annotations can (in their callout actually) so there's nothing you "need to do" or any configuration that you can enable.
A simple workaround to show titles on overlays is, as you suggest, to create a UILabel.
However, this UILabel should be added to an annotation view that is positioned at each overlay's center.
A minor drawback (or maybe not) to this method is that the titles will not scale with the zoom of the map -- they'll stay the same size and can eventually collide and overlay with other titles (but you may be ok with this).
To implement this approach:
For each overlay, add an annotation (using addAnnotation: or addAnnotations:) and set the coordinate to the approximate center of the overlay and the title to the overlay's title.
Note that since MKPolygon implements both the MKOverlay and the MKAnnotation protocols, you don't necessarily need to create a separate annotation class or separate objects for each overlay. MKPolygon automatically sets its coordinate property to the approximate center of the polygon so you don't need to calculate anything. You can just add the overlay objects themselves as the annotations. That's how the example below does it.
Implement the mapView:viewForAnnotation: delegate method and create an MKAnnotationView with a UILabel in it that displays the title.
Example:
[self.stateMapView addOverlays:overlays];
//After adding the overlays as "overlays",
//also add them as "annotations"...
[self.stateMapView addAnnotations:overlays];
//Implement the viewForAnnotation delegate method...
-(MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation
{
if ([annotation isKindOfClass:[MKUserLocation class]])
{
//show default blue dot for user location...
return nil;
}
static NSString *reuseId = #"ann";
MKAnnotationView *av = [mapView dequeueReusableAnnotationViewWithIdentifier:reuseId];
if (av == nil)
{
av = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:reuseId];
av.canShowCallout = NO;
//add a UILabel in the view itself to show the title...
UILabel *titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 50, 30)];
titleLabel.tag = 42;
titleLabel.backgroundColor = [UIColor clearColor];
titleLabel.textColor = [UIColor blackColor];
titleLabel.font = [UIFont boldSystemFontOfSize:16];
titleLabel.textAlignment = NSTextAlignmentCenter;
titleLabel.minimumScaleFactor = 0.5;
[av addSubview:titleLabel];
av.frame = titleLabel.frame;
}
else
{
av.annotation = annotation;
}
//find the UILabel and set the title HERE
//so that it gets set whether we're re-using a view or not...
UILabel *titleLabel = (UILabel *)[av viewWithTag:42];
titleLabel.text = annotation.title;
return av;
}
The alternative approach is to create a custom overlay renderer and do all the drawing yourself (the polygon line, the stroke color, the fill color, and the text). See Draw text in circle overlay and Is there a way to add text using Paths Drawing for some ideas on how to implement that.
This is my first question on this site.
My code is implemented in the following function using iOS 6 Mapkit, Objective-C.
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation
I have ONLY TWO annotations with custom pin images. One red pin and the other a orange pin. The red pin is closer to the user's current location within device map area and the other orange pin is 30 miles away (outside current map context). I am using different colors based on the data behind each pin.
Problem: Custom images for annotations are switching images.
Using all the tips on this site which includes usage of dequeueReusableAnnotationViewWithIdentifier, etc attached is my code.
On the very first display of the map on app launch, the error I see is that the orange pin displays on the device map and the red one is displayed outside. This is incorrect because the red image should have displayed on the device map.
If I tap the 'Find Me' button on the map to refresh my current location the red pin displays on the map which is now correct. When I tap 'Find Me' again, the red pin switches out to orange pin-- and it keeps switching or toggling pin image colors.
if (PinColor == 0) {
MKAnnotationView* pinview = (MKAnnotationView *)[self._mapView dequeueReusableAnnotationViewWithIdentifier:#"identifier"];
if (nil == pinview) {
MKAnnotationView* view = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"identifier"];
view.rightCalloutAccessoryView = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
view.opaque = YES;
view.image = [UIImage imageNamed:#"redpin.png"];
[view setCanShowCallout:YES];
if (self.newPinAdded) {
[view setSelected: YES];
}
return view;
}
return pinview;
}
else {
MKAnnotationView* pinview = (MKAnnotationView *)[self._mapView dequeueReusableAnnotationViewWithIdentifier:#"Orangeidentifier"];
if (nil == pinview) {
MKAnnotationView* view = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"Orangeidentifier"];
view.rightCalloutAccessoryView = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
view.image = [UIImage imageNamed:#"pin glabensmall.png"]; //orange pin
view.opaque = YES;
[view setCanShowCallout:YES];
if (self.newPinAdded) {
[view setSelected: YES];
}
return view;
}
return pinview;
}
I think your problem is a common one. Where is PinColor set? The mapview map ask for the annotationView to draw an annotation at any time for any annotation. If PinColor is set to 0 by some other method and then the mapview wants to draw an annotation any annotation it will draw a red pin. What you need to do is check which annotation you are drawing and then use the right colour for it. You can check the annotation by reading its title or if it is your own annotation class there may be some other property you can use.
SideNote: There are a few lines you've repeated for both pin version, you should have them outside the IF statement and cut out some lines. And you should use the mapView that viewForAnnotation give you:
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id < MKAnnotation >)annotation{
MKAnnotationView* pinview = nil;
if (annotation is the red one) {
pinview = (MKAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:#"identifier"];
if (nil == pinview) {
pinview = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"identifier"];
pinview.image = [UIImage imageNamed:#"redpin.png"];
}
} else {
pinview = (MKAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:#"Orangeidentifier"];
if (nil == pinview) {
pinview = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"Orangeidentifier"];
pinview.image = [UIImage imageNamed:#"pin glabensmall.png"]; //orange pin
}
}
[pinview setCanShowCallout:YES];
pinview.rightCalloutAccessoryView = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
pinview.opaque = YES;
if (self.newPinAdded) {
[pinview setSelected: YES];
}
}
- (MKAnnotationView *)mapView:(MKMapView *)theMapView viewForAnnotation:(id <MKAnnotation>)annotation
{
//if it's user location, return nil
if ([annotation isKindOfClass:[MKUserLocation class]])
return nil;
//try to dequeue an existing pin view first
static NSString* AnnotationIdentifier = #"AnnotationIdentifier";
MKPinAnnotationView* pinView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:AnnotationIdentifier];
pinView.animatesDrop = YES;
pinView.canShowCallout = YES;
pinView.pinColor = MKPinAnnotationColorRed;
//button on the right for popup for pins
UIButton* rightButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
[rightButton setTitle:annotation.title forState:UIControlStateNormal];
[rightButton addTarget:self
action:#selector(showDetails:) forControlEvents:UIControlEventTouchUpInside];
pinView.rightCalloutAccessoryView = rightButton;
//zoom button on the left of popup for pins
UIButton* leftButton = [UIButton buttonWithType:UIButtonTypeContactAdd];
[leftButton setTitle:annotation.title forState:UIControlStateNormal];
[leftButton addTarget:self
action:#selector(zoomToLocation:) forControlEvents:UIControlEventTouchUpInside];
pinView.leftCalloutAccessoryView = leftButton;
return pinView;
}
//for map view annotation right button
-(void)showDetails:(id)sender{
NSLog(#"Annotation Click");
//fypAppDelegate *appDelegate = (fypAppDelegate *)[[UIApplication sharedApplication] delegate];
//Attraction *attraction = (Attraction *)[appDelegate.attractions objectAtIndex:sender];
infoViewController *viewController = [self.storyboard instantiateViewControllerWithIdentifier:#"info"];
self.infoView = viewController;
[self.navigationController pushViewController:infoView animated:true];
}
//for map view annotation left button
-(void)zoomToLocation:(id)sender{
NSLog(#"Annotation Click");
}
Above is the delegate for the map annotations. I am able to show the pins and show the map annotation view but I don't know how to link the button events to the next view (infoViewController).
So as you guys can see, the right button is the one I want to use to enable user to view more information about that place while the left button, I want to allow user to zoom in into the coordinates of that pin.
The data are from the database I've created. Below is how I did it just for reference (in case you guys might need it)
-(void)putPins
{
fypAppDelegate *appDelegate = (fypAppDelegate *)[[UIApplication sharedApplication] delegate]; //get data
[appDelegate readTopAttractions];
int i = 0;
int count = appDelegate.attractions.count;
self.mapAnnotations = [[NSMutableArray alloc] initWithCapacity:appDelegate.attractions.count];
while (i < count) {
Attraction *attraction = (Attraction *)[appDelegate.attractions objectAtIndex:i];
i++;
//Set coordinates for pin
CLLocationCoordinate2D location;
location.latitude = (double)[[attraction xCoor] doubleValue];
location.longitude = (double)[[attraction yCoor] doubleValue];
MapPin *mapPin = [[MapPin alloc] init];
[mapPin setCoordinate:location];
[mapPin setName: [attraction name]];
NSString *desc = [attraction description];
int i = 0, position;
while(i < 50){
if ([desc characterAtIndex:i] == ' '){
position = i;
i++;
}
else
i++;
}
desc = [#"" stringByAppendingFormat:#"%#%#", [desc substringToIndex:position], #"..."];
[mapPin setDescription: desc];
[self.mapAnnotations addObject:mapPin];
}
[self.mapView addAnnotations:self.mapAnnotations];
}
please do tell me if you guys need more details.
Thank you! =)
In your showDetails: and zoomToLocation: methods, you can get a reference to the annotation whose callout button was tapped by doing the following:
MapPin *ann = (MapPin *)[mapView.selectedAnnotations objectAtIndex:0];
In zoomToLocation: you can then zoom in to that annotation using:
[mapView setRegion:
MKCoordinateRegionMakeWithDistance(ann.coordinate, 500, 500)
//500 meters vertical span, 500 meters horizontal span
animated:YES];
In showDetails:, you can pass ann or its properties to the detail view.
By the way, instead of calling custom methods using addTarget in viewForAnnotation, you can use the map view's calloutAccessoryControlTapped delegate method which gives more direct access to the annotation that was tapped. For example:
-(void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view
calloutAccessoryControlTapped:(UIControl *)control
{
MapPin *ann = (MapPin *)view.annotation;
if (control == view.rightCalloutAccessoryView)
{
NSLog(#"calloutAccessoryControlTapped: control=RIGHT");
//show detail view (or you can call your custom method here)...
}
else
if (control == view.leftCalloutAccessoryView)
{
NSLog(#"calloutAccessoryControlTapped: control=LEFT");
//zoom in (or you can call your custom method here)...
}
else
{
NSLog(#"calloutAccessoryControlTapped: unknown control");
}
}
Make sure you remove the addTarget calls from viewForAnnotation if you decide to use the calloutAccessoryControlTapped delegate method.
You want to zoom in to the particular pin? Is that right?
Therefore you can use the setRegion:animated: - method from MKMapView.
Example:
mapView = MKMapView
location = CLLLocationCoordinate2D
METERS_PER_MILE = 1609.344 (defined as a Constant)
MKCoordinateRegion region = MKCoordinateRegionMakeWithDistance(location, 0.5*METERS_PER_MILE, 0.5*METERS_PER_MILE);
MKCoordinateRegion adjustedRegion = [mapView regionThatFits:region];
[mapView setRegion:adjustedRegion animated:YES];
AppleDocs
http://developer.apple.com/library/ios/#documentation/MapKit/Reference/MKMapView_Class/MKMapView/MKMapView.html
http://developer.apple.com/library/IOs/#documentation/MapKit/Reference/MapKitDataTypesReference/Reference/reference.html
My app gets the points for a route from google. The response also contains the bounding rect. My app creates the rect and shows the map correctly and I add the overlay. The
- (MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id <MKOverlay>)
overlay is called and I construct a MKOverlayPathView from the points. I have verified that the points are within the bounding rect. However, the route overlay does not draw on the displayed map.
I have checked everything I can think of with no joy, any suggestions would be greatly appreciated.
I don't know yet why the overlay isn't showing but try the following code in a fresh, new project.
In my test, I added the map view control to the xib and then connected the IBOutlet and the map view's delegate outlet to File's Owner. Creating the map view in code will also work (just don't also add it to the xib and be sure to set the delegate property).
This is my test TRTrip class:
//TRTrip.h...
#interface TRTrip : NSObject<MKOverlay>
#property (nonatomic, readonly) MKMapRect boundingMapRect;
#property (nonatomic, readonly) CLLocationCoordinate2D coordinate;
#end
//TRTrip.m...
#implementation TRTrip
#synthesize boundingMapRect;
#synthesize coordinate;
-(MKMapRect)boundingMapRect {
return MKMapRectWorld;
}
-(CLLocationCoordinate2D)coordinate {
return CLLocationCoordinate2DMake(42.3507,-71.0608);
}
#end
In the view controller's viewDidLoad, I add an MKPointAnnotation, MKCircle, TRTrip, and an MKPolyline to the map:
- (void)viewDidLoad
{
[super viewDidLoad];
CLLocationCoordinate2D bostonCoord = CLLocationCoordinate2DMake(42.3507,-71.0608);
//center map on Boston...
mMapView.region = MKCoordinateRegionMakeWithDistance(bostonCoord, 30000, 30000);
//add boston annotation...
MKPointAnnotation *pa = [[MKPointAnnotation alloc] init];
pa.coordinate = bostonCoord;
pa.title = #"Boston";
[mMapView addAnnotation:pa];
[pa release];
//add MKCircle overlay...
MKCircle *circle = [MKCircle circleWithCenterCoordinate:bostonCoord radius:10000];
[mMapView addOverlay:circle];
//add TRTrip overlay...
TRTrip *trt = [[TRTrip alloc] init];
[mMapView addOverlay:trt];
[trt release];
//NOTE:
//Using an MKPolyline and MKPolylineView is probably easier than
//manually drawing lines using MKOverlayPathView.
//add MKPolyline overlay...
int numberOfRouteCoords = 3;
CLLocationCoordinate2D *routeCoords = malloc(numberOfRouteCoords * sizeof(CLLocationCoordinate2D));
routeCoords[0] = CLLocationCoordinate2DMake(42.34, -71.1);
routeCoords[1] = CLLocationCoordinate2DMake(42.25, -71.05);
routeCoords[2] = CLLocationCoordinate2DMake(42.3, -71.02);
MKPolyline *pl = [MKPolyline polylineWithCoordinates:routeCoords count:numberOfRouteCoords];
[mMapView addOverlay:pl];
free(routeCoords);
}
and this is the viewForOverlay method:
-(MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id<MKOverlay>)overlay
{
if ([overlay isKindOfClass:[MKCircle class]])
{
MKCircleView *cv = [[[MKCircleView alloc] initWithCircle:overlay] autorelease];
cv.fillColor = [UIColor greenColor];
cv.strokeColor = [UIColor blueColor];
cv.alpha = 0.5;
return cv;
}
if ([overlay isKindOfClass:[TRTrip class]])
{
MKOverlayPathView *opv = [[[MKOverlayPathView alloc] initWithOverlay:overlay] autorelease];
opv.strokeColor = [UIColor redColor];
opv.lineWidth = 3;
CGMutablePathRef myPath = CGPathCreateMutable();
MKMapPoint mp1 = MKMapPointForCoordinate(CLLocationCoordinate2DMake(42.3507,-71.1));
CGPoint cgp1 = [opv pointForMapPoint:mp1];
CGPathMoveToPoint(myPath, nil, cgp1.x, cgp1.y);
MKMapPoint mp2 = MKMapPointForCoordinate(CLLocationCoordinate2DMake(42.45,-71.05));
CGPoint cgp2 = [opv pointForMapPoint:mp2];
CGPathAddLineToPoint(myPath, nil, cgp2.x, cgp2.y);
MKMapPoint mp3 = MKMapPointForCoordinate(CLLocationCoordinate2DMake(42.3,-71.0));
CGPoint cgp3 = [opv pointForMapPoint:mp3];
CGPathAddLineToPoint(myPath, nil, cgp3.x, cgp3.y);
opv.path = myPath;
CGPathRelease(myPath);
return opv;
}
if ([overlay isKindOfClass:[MKPolyline class]])
{
MKPolylineView *plv = [[[MKPolylineView alloc] initWithPolyline:overlay] autorelease];
plv.strokeColor = [UIColor purpleColor];
plv.lineWidth = 5;
return plv;
}
return nil;
}
Here is the result:
The red line on top is the TRTrip and the purple one on the bottom is the MKPolyline.
If your example is a bit more complex then this, check if you have any calls like this:
[mMapView removeOverlay:overlay]
in the code.