I am trying to change the pin colour from the default red to a custom image but whatever I am trying just isn't working.
I have downloaded the sample code from this website:
http://icodeblog.com/2009/12/21/introduction-to-mapkit-in-iphone-os-3-0/
The code works on it's own but when I import the annotation classes into my code, they do not work and I have no idea why. I have also tried a number of different methods from other sites but I can't even get the pin colour to change.
My project has 4 tabs and the MapView is on one of the tabs. When I select it, it parses a JSON string and adds the separate annotations onto the map. i have the title and subtitle showing up when I click on the pin, but cannot change the colour or image.
Here is how I add my annotations - MapAnnotation follows MKAnnotation:
MapAnnotation *ann = [[MapAnnotation alloc] initWithCoordinate:newCoord];
ann.title = [locationDictionary objectForKey:#"name"];
ann.subtitle = [locationDictionary objectForKey:#"name"];
[mapView addAnnotation:ann];
[ann release]
Here is how I try and attempt to change the colour - I have MapView.delegate=self in the view controller:
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation{
MKPinAnnotationView *pin = (MKPinAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:[annotation title]];
if (pin == nil) {
pin = [[[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:[annotation title]] autorelease];
}else {
pin.annotation = annotation;
}
pin.pinColor = MKPinAnnotationColorGreen;
pin.animatesDrop = YES;
pin.canShowCallout = TRUE;
return pin;
}
I get the annotations to appear with title and subtitle, just not green markers. They're always red wether I use colours or images. If anyone could help me out, that would be great!
Thanks
EDIT:
The mapView delegate is assigned in the viewDidLoad method. I also add an Overlay to a certain part of the map. This is working fine, I have also taken it out and tried it without that incase it was causing a problem but it still didn't fix it.
- (void)viewDidLoad {
[super viewDidLoad];
MKCoordinateRegion cordRgn;
LusuAppAppDelegate *delegate =[[UIApplication sharedApplication]delegate];
if (delegate.CurrentLocation == 0) {
cordRgn.center.latitude = (CENTRE_OF_POSITION_2_LAT);
cordRgn.center.longitude = (CENTRE_OF_POSITION_2_LON);
delegate.CurrentLocation = 1;
}else if (delegate.CurrentLocation == 1) {
cordRgn.center.latitude = (CENTRE_OF_POSITION_1_LAT);
cordRgn.center.longitude = (CENTRE_OF_POSITION_1_LON);
delegate.CurrentLocation = 0;
}
cordRgn.span.latitudeDelta = 0.009f;
cordRgn.span.longitudeDelta = 0.009f;
self.locationManager = [[CLLocationManager alloc] init];
locationManager.delegate = self;
locationManager.desiredAccuracy = kCLLocationAccuracyBest;
[locationManager startUpdatingLocation];
[locationManager startUpdatingHeading];
//add overlay
MKRasterOverlay *overlay = [[MKRasterOverlay alloc] init];
[overlay setCoordinate:CLLocationCoordinate2DMake(54.005508, -2.780507)];
MKMapRect mkrect;
MKMapPoint mkpointa, mkpointb;
mkrect.origin = MKMapPointForCoordinate(CLLocationCoordinate2DMake(54.016964, -2.794862));
mkpointa = MKMapPointForCoordinate(CLLocationCoordinate2DMake(54.016964, 2.000000));
mkpointb = MKMapPointForCoordinate(CLLocationCoordinate2DMake(54.001447, 2.013770));
mkrect.size.width = mkpointb.x - mkpointa.x;
mkrect.size.height = mkpointb.y - mkpointa.y;
overlay.boundingMapRect = mkrect;
mapView.delegate = self;
[mapView addOverlay:overlay];
[mapView setRegion:cordRgn animated:NO];
[self.mapView setShowsUserLocation:YES];
[self doAnnotations];
}
The doAnnotations function is the code shown above but in a loop. Thanks again for your help.
Your code looks fine to me. I suspect you have an error in your delegate assignment and mapView:viewForAnimation is not actually being called. An MKMapView without a delegate providing that function will work fine, with red pins for all annotations. Try adding some NSLog statements or setting a breakpoint in the debugger to make sure you're actually executing this code.
You probably already know this, but using your own images will require you to create pins that are MKAnnotationViews rather than MKAnnotationPinViews.
Related
I observed a strange behavior with the map in iOS 6.
Here is a code which adds a single annotation, absolutely nothing else in the project:
- (void)viewDidLoad
{
[super viewDidLoad];
MKPointAnnotation * p = [[MKPointAnnotation alloc] init];
p.coordinate = CLLocationCoordinate2DMake(10, 10);
p.title = #" test test";
[self.mapView addAnnotation:p];
self.mapView.centerCoordinate = p.coordinate;
}
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id < MKAnnotation >)annotation
{
MKPinAnnotationView * p = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"wassup" ];
p.canShowCallout = YES;
p.pinColor = MKPinAnnotationColorGreen;
return p;
}
When it is selected and I move (pan) the map small distances like 0.5-1 cm, on iOS6 the annotation gets deselected. On earlier versions works as expected and remains selected.
Is it a bug ? Is there any workaround for this ?
Thanks !
Apple maps are full of bugs.. LOL you noticed that too..
Its a feature of iOS 6 sometime can irritate the the user.. :)
in annotaionView method use:
// iOS6 BUG WORKAROUND !!!!!!!
if (is6orMore) {
[annotationView setTransform:CGAffineTransformMakeRotation(.001)]; //any small positive rotation
}
Also see the link
If you want to not deselect an annotation view, you should subclass MKAnnotationView and override the setSelected:animated: method and stop the annotation view from being deselected.
I am trying to add annotations and overlays to a mapview but it crashes randomly. It is an EXC_BAD_ACCESS error, but zombies doesn't tell me anything. It says it is crashing on CG::Path::apply_transform(CGAffineTransform const&). I have looked everywhere for why this is happening but can't pinpoint it.
I am creating the mapview in ib and have the delegates and everything set up right. It will work sometimes and then crash randomly.
I am using a gesture recognizer to add the annotations and overlay
UITapGestureRecognizer *doubleTap = [[UITapGestureRecognizer alloc]
initWithTarget:self action:#selector(handleDoubleTap:)];
[doubleTap setNumberOfTapsRequired:2];
[self.mapView addGestureRecognizer:doubleTap];
and
- (void)handleDoubleTap:(UIGestureRecognizer *)gestureRecognizer
{
if (gestureRecognizer.state == UIGestureRecognizerStateRecognized){
CGPoint touchPoint = [gestureRecognizer locationInView:self.mapView];
CLLocationCoordinate2D touchMapCoordinate =
[self.mapView convertPoint:touchPoint toCoordinateFromView:self.mapView];
//add pin where user touched down...
MKPointAnnotation *pa = [[MKPointAnnotation alloc] init];
pa.coordinate = touchMapCoordinate;
//[pa setTitle:#"title"];
[mapView addAnnotation:pa];
MKCircle* circle=[MKCircle circleWithCenterCoordinate:touchMapCoordinate radius:500];
[mapView addOverlay:circle];
}
}
And the views for each:
-(MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id)overlay
{
if ([overlay isKindOfClass:[MKCircle class]]) {
MKCircleView* circleView = [[MKCircleView alloc] initWithOverlay:overlay];
circleView.strokeColor = [UIColor redColor];
circleView.lineWidth = 1.0;
circleView.fillColor = [UIColor blackColor];
circleView.alpha=.5;
return circleView;
}
else
return nil;
}
- (MKAnnotationView *)mapView:(MKMapView *)localmapView viewForAnnotation:(id <MKAnnotation>)annotation
{
if (![annotation isKindOfClass:[MKUserLocation class]]) {
static NSString *AnnotationIdentifier = #"Annotation";
MKPinAnnotationView* pinView = (MKPinAnnotationView *)[localmapView dequeueReusableAnnotationViewWithIdentifier:AnnotationIdentifier];
if (!pinView)
{
pinView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:AnnotationIdentifier];
pinView.pinColor = MKPinAnnotationColorRed;
pinView.animatesDrop = YES;
}
else
{
pinView.annotation = annotation;
}
return pinView;
}
return nil;
}
Is there a better way to add annotations/overlays to maps with user interaction? Am I doing something wrong in this code? It appears to draw most of the circle but then crashes... Is there some special trick to mapviews?
I've been getting the exact same error:
CG::Path::apply_transform(CGAffineTransform const&) will hit a test instruction and give me an EXC_BAD_ACCESS
This specifically occurs when using a double click on the map to zoom in on an MKCircle.
I can't say this definitively, but to the best of my knowledge this problem only occurs on the simulator when you use a double click to zoom, I've never been able to cause the error from an actual device, or by using option+click to zoom on the simulator.
So at this point I've filed this under "simulator bug" and left it at that.
If you do discover anything to the contrary, please let me know because it really bothers me not explicitly knowing whether or not this is a bug existing in my app that I just can't properly reproduce.
edit:
This was flagged initially as "not an answer" so I'll provide a little bit more information supporting my conjecture.
Basically in both of our scenarios a gesture is firing the re-rendering of the MKCircleView, what I strongly suspect is that since the simulator is able to generate some kind of gesture which can't be created from a user on the actual device, there is a failed expectation somewhere down the chain while that gesture gets handled.
I am not sure where your EXC_BAD_ACCESS problems is. But you have a big problem with leaking memory. You have to releas object that you create with init. In the above code you create objects and never release them. That will not throw EXC_BAD_ACCESS but it will consume your memory.
Release the following object :
MKPointAnnotation *pa = [[MKPointAnnotation alloc] init];
MKCircleView* circleView = [[MKCircleView alloc] initWithOverlay:overlay];
I'm currently adding annotations to my map through a loop... but the annotations are only appearing on my map in groups. Also, on load, only about 4 annotations are actually displayed on the map... but as I move the map a little, all of the annotations that should be there, suddenly appear.
How can I get all of the annotations to load in the right place, one at a time?
Thanks in advance!
Here is the code I'm using to add annotations:
NSString *incident;
for (incident in weekFeed) {
NSString *finalCoordinates = [[NSString alloc] initWithFormat:#"%#", [incident valueForKey:#"coordinates"]];
NSArray *coordinatesArray = [finalCoordinates componentsSeparatedByString:#","];
latcoord = (#"%#", [coordinatesArray objectAtIndex:0]);
longcoord = (#"%#", [coordinatesArray objectAtIndex:1]);
// Final Logs
NSLog(#"Coordinates in NSString: [%#] - [%#]", latcoord, longcoord);
CLLocationCoordinate2D coord;
coord.latitude = [latcoord doubleValue];
coord.longitude = [longcoord doubleValue];
DisplayMap *ann = [[DisplayMap alloc] init];
ann.title = [NSString stringWithFormat: #"%#", [incident valueForKey:#"incident_type"]];
ann.subtitle = [NSString stringWithFormat: #"%#", [incident valueForKey:#"note"]];
ann.coordinate = coord;
[mapView addAnnotation:ann];
[ann release];
}
// Custom Map Markers
-(MKAnnotationView *)mapView:(MKMapView *)map viewForAnnotation:(id <MKAnnotation>)annotation {
if ([annotation isKindOfClass:[MKUserLocation class]])
return nil; //return nil to use default blue dot view
static NSString *AnnotationViewID = #"annotationViewID";
MKAnnotationView *annotationView = (MKAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:AnnotationViewID];
if (annotationView == nil) {
annotationView = [[[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:AnnotationViewID] autorelease];
}
annotationView.canShowCallout = YES;
if ([annotationView.annotation.title isEqualToString:#"one"]) {
UIImage *pinImage = [UIImage imageNamed:#"marker_1.png"];
[annotationView setImage:pinImage];
}
if ([annotationView.annotation.title isEqualToString:#"two"]) {
UIImage *pinImage = [UIImage imageNamed:#"marker_2.png"];
[annotationView setImage:pinImage];
}
annotationView.annotation = annotation;
return annotationView;
}
- (void) mapView:(MKMapView *)mapV didAddAnnotationViews:(NSArray *)views {
CGRect visibleRect = [mapV annotationVisibleRect];
for (MKAnnotationView *view in views) {
CGRect endFrame = view.frame;
CGRect startFrame = endFrame; startFrame.origin.y = visibleRect.origin.y - startFrame.size.height;
view.frame = startFrame;
[UIView beginAnimations:#"drop" context:NULL];
[UIView setAnimationDuration:0.4];
view.frame = endFrame;
[UIView commitAnimations];
}
}
Adam,
This solution is a bit messy as I had to munge up one of my current projects to test, but hopefully this will work for you.
First an explanation, it's critical to separate data from UI presentation. The [MKMapView addAnnotation(s)] are just a data update to MKMapView and have no direct impact on animation or timing.
The delegate method mapView:didAddAnnotationViews: is where all of the custom presentation behavior should be defined. In your description you didn't want these to appear all at once, so you need to sequence your animations instead of performing them simultaneously.
One method is to add all of the annotations at once and then just add them with increasing animation delays, however new annotations that get added for whatever reason will begin their animations at zero again.
The method below sets up an animation queue self.pendingViewsForAnimation (NSMutableArray) to hold annotation views as they are added and then chains the animation sequentially.
I've replaced the frame animation with alpha to focus on the animation problem to separate it from the issue of some items not appearing. More on this after the code...
// Interface
// ...
// Add property or iVar for pendingViewsForAnimation; you must init/dealloc the array
#property (retain) NSMutableArray* pendingViewsForAnimation;
// Implementation
// ...
- (void)processPendingViewsForAnimation
{
static BOOL runningAnimations = NO;
// Nothing to animate, exit
if ([self.pendingViewsForAnimation count]==0) return;
// Already animating, exit
if (runningAnimations)
return;
// We're animating
runningAnimations = YES;
MKAnnotationView* view = [self.pendingViewsForAnimation lastObject];
[UIView animateWithDuration:0.4 animations:^(void) {
view.alpha = 1;
NSLog(#"Show Annotation[%d] %#",[self.pendingViewsForAnimation count],view);
} completion:^(BOOL finished) {
[self.pendingViewsForAnimation removeObject:view];
runningAnimations = NO;
[self processPendingViewsForAnimation];
}];
}
// This just demonstrates the animation logic, I've removed the "frame" animation for now
// to focus our attention on just the animation.
- (void) mapView:(MKMapView *)mapV didAddAnnotationViews:(NSArray *)views {
for (MKAnnotationView *view in views) {
view.alpha = 0;
[self.pendingViewsForAnimation addObject:view];
}
[self processPendingViewsForAnimation];
}
Regarding your second issue, items are not always appearing until you move the map. I don't see any obvious errors in your code, but here are some things I would do to isolate the problem:
Temporarily remove your mapView:didAddAnnotationViews:, mapView:annotationForView: and any other custom behaviors to see if default behavior works.
Verify that you have a valid Annotation at the addAnnotation: call and that the coordinates are visible (use [mapView visibleMapRect], MKMapRectContainsPoint(), and MKMapPointForCoordinate().
If it is still not functioning, look at where you are calling the add annotations code from. I try to avoid making annotation calls during map movement by using performSelector:withObject:afterDelay. You can precede this with an [NSObject cancelPreviousPerformRequestsWithTarget:selector:object:] to create a slight delay prior to loading annotations in case the map is being moved a long distance with multiple swipes.
One last point, to achieve the pin-drop effect you're looking for, you probably want to offset by a fixed distance from the original object instead of depending on annotationVisibleRect. Your current implementation will result in pins moving at different speeds depending on their distance from the edge. Items at the top will slowly move into place while items at the bottom will fly rapidly into place. Apple's default animation always drops from the same height. An example is here: How can I create a custom "pin-drop" animation using MKAnnotationView?
Hope this helps.
Update:
To demonstrate this code in action I've attached a link to a modified version of Apple's Seismic demo with the following changes:
Changed Earthquake.h/m to be an MKAnnotation object
Added SeismicMapViewController.h/m with above code
Updated RootViewController.h/m to open the map view as a modal page
See: http://dl.dropbox.com/u/36171337/SeismicXMLWithMapDelay.zip
You will need to pause updating after you add each annotation and allow the map to have time to refresh.
- (void) setTheMap
{
myMap.mapType = MKMapTypeStandard;
[myMap setDelegate:self];
CLLocationCoordinate2D coord = {latitude:myValue , longitude:myValue };
MKCoordinateSpan span = {latitudeDelta:0.05f , longitudeDelta: 0.05f};
MKCoordinateRegion region = {coord , span};
[myMap setRegion:region];
[self.view addSubview:myMap];
PantryAnnotation *dis = [[PantryAnnotation alloc] init];
dis.coordinate = region.center;
dis.title = row.panName;
dis.subTitle = row.geo_lat;
[myMap addAnnotation:dis];
}
- (MKAnnotationView *)mapView:(MKMapView *)mV viewForAnnotation:(id<MKAnnotation>)annotation
{ MKPinAnnotationView *pview = nil;
if (annotation != myMap.userLocation)
{
static NSString *defalt = #"Volunteer";
pview = (MKPinAnnotationView *)[myMap dequeueReusableAnnotationViewWithIdentifier:defalt];
if ( pview == nil )
pview = [[[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:defalt] autorelease];
pview.pinColor = MKPinAnnotationColorRed;
pview.canShowCallout = YES;
pview.animatesDrop = YES;
}
else
{
[myMap.userLocation setTitle:#"I am Here"];
}
return pview;
}
The above gives me map view with annotation pin. I have to write this code in more than 8 files. Can any one tell me how can I centralized this code ? I mean to create only one class and by object of that class I can add the map in any other view.
Thanks..
Since the rest of your code is not clear, it is hard to give advice, but you may try to analyze it first;
what is common in these 8 code parts, and what is different?
Then you should figure out if you need to create and hold references to any local variables, lets say your map objects, or something like that.
If so, then subclassing would be the first choice to look at,
if not then an Obj-C category would probably do (http://macdevelopertips.com/objective-c/objective-c-categories.html). P.S: you cannot add member fields with this, only behavior (methods) are allowed.
If you need only objects with little to none behavior other than these, then you may make them plain objects holding the parameters only.
Or even you may use static methods like ( +(void) blahblah; ) just to be short, but dont do it unless you are sure that the behavior is generic in nature...
Well, a soup of advice here, if you provide more info, i can write in one of these directions in more details
good luck!
I use this tutorial for integrating MapKit to my application:
http://iphonebcit.wordpress.com/iphone-map-kit-tutorial/
CLLocationCoordinate2D coordinate;
coordinate.latitude = 49.2802;
coordinate.longitude = -123.1182;
NSUInteger count = 1;
for(int i = 0; i < 10; i++) {
CGFloat latDelta = rand()*.035/RAND_MAX - .02;
CGFloat longDelta = rand()*.03/RAND_MAX - .015;
CLLocationCoordinate2D newCoord = {coordinate.latitude+latDelta, coordinate.longitude+longDelta};
MapDemoAnnotation* annotation = [[MapDemoAnnotation alloc] initWithCoordinate:newCoord andID:count++];
[mapView addAnnotation:annotation];
[annotation release];
}
and
- (MKAnnotationView *)mapView:(MKMapView *)mapViewLocal viewForAnnotation:(id <MKAnnotation>)annotation {
MKPinAnnotationView *pinView = (MKPinAnnotationView*)[mapViewLocal dequeueReusableAnnotationViewWithIdentifier:#"Pin"];
if(pinView == nil) {
pinView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"Pin"];
pinView.pinColor = MKPinAnnotationColorPurple;
pinView.rightCalloutAccessoryView = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
pinView.animatesDrop = YES;
pinView.canShowCallout = YES;
} else {
pinView.annotation = annotation;
}
return pinView;
}
So the pins will set on the map randomly. In my application, the coordinates will change. How can I change the coordinates of the annotations, so they will be updated on the map?
Does anyone know?
At iPhone SDK 3.x you have to remove the pin annotations and set it again. That is not very nice if you have many annotations an your map.
I try to make it better so I ony display/renew my pin annotations which are on the screen. So if an user zoom in to New York, there won't be pin annotations in San Francisco or other than the user can't see. So the performance will be much better.
Perhaps in the future this would be possible. I hope so :-)
That tutorial is only for getting a map view to show, nothing else. You're going to need a bigger tutorial. I found this one useful:
http://blog.objectgraph.com/index.php/2009/04/02/iphone-sdk-30-playing-with-map-kit/
Probably what you'll need to do is to loop through all the annotations on the map, removing them, then loop through your data array, adding the annotations back again. You could be more clever about it and loop through the data array, checking if there's already a pin with the same latitude/longitude on the map, but that way gets more complicated.
Since I'm doing annotations at the moment I just made a quick test. You do get a compiler warning, so it might not be supported. But it works.
Make a custom MKAnnotation class so you can set the coordinate property to be writable:
#property (nonatomic, readwrite) CLLocationCoordinate2D coordinate;
Then at whatever event or interval you want, change the coordinate using something like this:
CLLocation *loc=[[CLLocation alloc] initWithLatitude:55.0 longitude:17.0];
annotation.coordinate=loc.coordinate;
[loc release];