Get selected pin coordinate using MapKit framework (Objective-C) - objective-c

I have a map region with about ten annotated MapPin objects (coordinates retrieved from a plist). In the code below 'locations' is a NSMutable array object containing the pin annotation latitude and longitude.
for (int i = 0; i<[locations count]; i++) {
MKCoordinateSpan span = MKCoordinateSpanMake(0, 0);
CLLocationCoordinate2D center = CLLocationCoordinate2DMake(0, 0);
pinRegion = MKCoordinateRegionMake(center, span);
MapPin *pin = [[MapPin alloc] init];
pinRegion.center.longitude = [locations[i][0] doubleValue];
pinRegion.center.latitude = [locations[i][1] doubleValue];
pin.title = names[i];
pin.coordinate = pinRegion.center;
[self.mapView addAnnotation:pin];
}
By selecting any pin I want to return its coordinate. I can inspect any pin object address like this:
NSLog(#"%#", self.mapView.selectedAnnotations);
... shows a unique address for a selected pin such as "<MapPin: 0x16d2f610>"
But I don't know how to access the objects coordinate properties such as longitude and latitude.
Please can you help?
Thank you!

Make sure your map view has its delegate assigned (in this example we will use your view controller)
mapView.delegate = self
Next handle the callback delegate for annotation taps like this:
Swift
func mapView(mapView: MKMapView, didSelectAnnotationView view: MKAnnotationView)
{
if let annotationCoordinate = view.annotation?.coordinate
{
print("User tapped on annotation with title: \(annotationCoordinate")
}
}
Objective - C
- (void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view{
NSLog(view.annotation.coordinate);
}
Let me know if you have any questions

Related

MKOverlay doesn't render a second time on iOS7

I have a map view that has annotations on it. With each annotation, I need to show an overlay that is just a circle. This circle needs to be one of two different colours depending on a value. The user can select something else on the screen, and all annotations and overlays need to be removed, and new ones need to be added based on the newly selected item.
The first time I load the controller having the map view, everything works great. When the user selects a new item, the old annotations and overlays are removed, and the new annotations and overlays are added, but the overlays don't appear. When I put breakpoints in, I don't see the mapview:rendererForOverlay method getting called after a user has selected a new item. Below is the code I am using:
In viewDidLoad, I have the following:
_mapview.delegate = self;
[self placePinsOnMap];
The placePinsOnMap: method has the following:
- (void)placePinsOnMap {
AMapAnnotation* annotation;
for (APlace* place in _selectedItem.places) {
annotation = [[AMapAnnotation alloc] initWithTitle:place.name subtitle:place.subtitle coordinate:place.location];
annotation.object = place;
[_mapview addAnnotation:annotation];
[_mapview addOverlay:[[AMapOverlay alloc] initWithCenterCoordinate:place.location radius:[_place.size floatValue]*1000 object:place] level:MKOverlayLevelAboveRoads];
}
}
Each time an overlay is added, the mapview:rendererForOverlay is called. This looks like this:
- (MKOverlayRenderer*)mapView:(MKMapView *)mapView rendererForOverlay:(id<MKOverlay>)overlay {
MKCircleRenderer* circleRenderer;
AMapOverlay* mapOverlay;
APlace* place;
mapOverlay = (AMapOverlay*)overlay;
place = mapOverlay.object;
circleRenderer = [[MKCircleRenderer alloc] initWithCircle:mapOverlay];
if ([place.radius floatValue] >= 5 && [place.radius floatValue] <= 10) {
circleRenderer.fillColor = [[UIColor greenColor] colorWithAlphaComponent:0.2];
} else {
circleRenderer.fillColor = [[UIColor redColor] colorWithAlphaComponent:0.2];
}
return circleRenderer;
}
When a user selects a new item, I remove all annotations and overlays, and I place the new annotations and their overlays for the new item selected:
- (void)onScrollviewTap:(UITapGestureRecognizer*)recognizer {
CGPoint point;
point = [recognizer locationInView:_scrollview];
for (ACard* card in _cards) {
if (CGRectContainsPoint(card.frame, point)) {
card.selected = YES;
_selectedItem = card.item;
NSArray* array = [_mapview annotations];
for (id<MKAnnotation> annotation in array) {
if (annotation != _userAnnotation) {
[_mapview removeAnnotation:annotation];
}
}
[_mapview removeOverlays:[_mapview overlays]];
[self placePinsOnMap];
} else {
card.selected = NO;
}
}
}
The AMapOverlay class is a subclass of MKCircle and simply holds the coordinate, a radius value and a place object which is used to determine what colour the overlay should be. I have the boundingMapRect overridden in the AMapOverlay class:
- (MKMapRect)boundingMapRect {
MKMapPoint upperLeft = MKMapPointForCoordinate(self.coordinate);
MKMapRect bounds = MKMapRectMake(upperLeft.x, upperLeft.y, self.radius*2, self.radius*2);
return bounds;
}
Just to reiterate what my problem is: The first time I load the controller having the map view, everything works great. I see the annotations along with their overlays. When the user selects a new item, the old annotations and overlays are removed, and the new annotations are added along with the overlays, but the overlays don't appear.
Does anyone have any ideas why this would be?

Make CCSprite move towards other CCSprite

I know this has been asked a lot before, but I can't find the solution. I have a CCSprite on the screen, the player, that is steered with the accelerometer. Then on top of the screen other CCSprites are spawned every 2 seconds, the enemies. I want all the enemies to follow the player, if the player moves the player the enemies should change direction and go towards that CCSprite. This is my code this far:
- (void)spawnEnemies
{
monsterTxt = [[CCTexture2D alloc] initWithCGImage:[UIImage imageNamed:#"obj.png"].CGImage resolutionType:kCCResolutionUnknown];
monster = [CCSprite spriteWithTexture:monsterTxt];
...
//random spawn position etc.
CCMoveTo *movemonster = [CCMoveTo actionWithDuration:7.0 position:ccp(_rocket.boundingBox.origin.x, _rocket.boundingBox.origin.y)];
[monster runAction:[CCSequence actions:movemonster, nil]];
[_monsters addObject:monster]; //adds the sprite to a mutable array
}
- (void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration
{
...
//determines new position and move sprite there
[monster stopAllActions];
CCMoveTo *movemonster = [CCMoveTo actionWithDuration:7.0 position:ccp(_rocket.boundingBox.origin.x, _rocket.boundingBox.origin.y)];
[monster runAction:[CCSequence actions:movemonster, nil]];
}
Now when I start the game the sprites are going towards the player, but when the player moves the enemies doesn't update their destination, they just continue down and stops at the y-coordinate of the player. And after a while the app crashes. What am I doing wrong?
My guess is that the problem may be in that section of code that you did not post.
In v2.1 of Cocos2d, to receive accelerometer measurements in your layer:
Do this in your init or onEnterTransitionDidFinish call:
self.accelerometerEnabled = YES;
And overload the is method:
-(void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration
{
CCLOG(#"Acceleration (%f,%f,%f)",
acceleration.x,
acceleration.y,
acceleration.z);
// Do something meaningful...you probably want to send a notification to
// some other scene/layer/logic. It should cache that the acceleration value
// and then do something on an update(...) call with it. Then reset it so that
// it does not get used the next update cycle untila new value comes in (here).
}
It seems you have this...and you have indicated you are receiving them...so I'm a bit confused.
SO (SANITY CHECK TIME)...
I created an example project (here) which has a single player sprite being chased by multiple monster sprites. When the user touches the screen (I did not use the accelerometer), the player sprite changes location and the sprites "chase" him. I added some random offset to their target positions so they would not all cluster on top of the player.
Here is the code for (the most important parts of) the file, which is a modified version of the HellowWorldLayer that comes with a cocos2d v2.1 project (I created it from the template):
-(void)createPlayer
{
playerMoved = NO;
if(self.player != nil)
{
[self removeChild:player];
self.player = nil;
}
CGSize scrSize = [[CCDirector sharedDirector] winSize];
CCSprite* playerSprite = [CCSprite spriteWithFile:#"Icon.png"];
playerSprite.scale = 2.0;
playerSprite.position = ccp(scrSize.width/2,scrSize.height/2);
[self addChild:playerSprite];
self.player = playerSprite;
}
-(void)createMonsters
{
const int MAX_MONSTERS = 4;
if(self.monsters.count > 0)
{ // Get rid of them
for(CCSprite* sprite in monsters)
{
[self removeChild:sprite];
}
[self.monsters removeAllObjects];
}
CGSize scrSize = [[CCDirector sharedDirector] winSize];
for(int idx = 0; idx < MAX_MONSTERS; idx++)
{
CCSprite* sprite = [CCSprite spriteWithFile:#"Icon.png"];
float randomX = (arc4random()%100)/100.0;
float randomY = (arc4random()%100)/100.0;
sprite.scale = 1.0;
sprite.position = ccp(scrSize.width*randomX,scrSize.height*randomY);
[self addChild:sprite];
[monsters addObject:sprite];
}
}
-(void)update:(ccTime)delta
{
if(playerMoved)
{ // Modify all the actions on all the monsters.
CGPoint playerPos = player.position;
for(CCSprite* sprite in monsters)
{
float randomX = (1.0)*(arc4random()%50);
float randomY = (1.0)*(arc4random()%50);
CGPoint position = ccp(playerPos.x+randomX,playerPos.y+randomY);
[sprite stopAllActions];
[sprite runAction:[CCMoveTo actionWithDuration:3.0 position:position]];
}
playerMoved = NO;
}
}
I have a retained array of CCSprite* objects (monsters) and a CCSprite* for the player. I have a flag to tell me if the player has changed position (player moved). In the update loop, if the player has moved, stop all the actions on the monsters and update them.
On the screen it looks like this:
And then when I move the main sprite...
They follow it...
Was this helpful?

MKPointAnnotation not working

Hello i got this method:
-(void)adresseZeigen
{
NSLog(#"%s",__PRETTY_FUNCTION__);
selectedAnnotation = [[MKPointAnnotation alloc]init];
selectedAnnotation.title = selectedCompanyName;
selectedAnnotation.subtitle = selectedCompanyAdresse;
selectedAnnotation.coordinate = selectedCompanyPoint;
NSLog(#"selected title: %#",selectedAnnotation.title);
NSLog(#"selected subtitle: %#",selectedAnnotation.subtitle);
NSLog(#"selected latitude is: %f", self.selectedAnnotation.coordinate.latitude );
NSLog(#"selected longitude is: %f", self.selectedAnnotation.coordinate.longitude );
[mapView addAnnotation:selectedAnnotation];
MKCoordinateRegion selectedRegion;
selectedRegion.center = selectedCompanyPoint;
selectedRegion.span.longitudeDelta = 0.01;
selectedRegion.span.latitudeDelta = 0.01;
[mapView setRegion:selectedRegion animated:YES];
}
it should actually give me a annotation in my mapview.
My Log output is:
-[SecondViewController adresseZeigen]
selected title: Company 2
selected subtitle: Company 2 Adresse
selected latitude is: 48.620000
selected longitude is: 9.460000
but somehow I dont get a annotation on map.
Can someone please help me?
This alone is not gonna give you any actual annotation views on the map. In order to have annotation views in your map, you need to declare the MKMapViewDelegate for your view controller, declare it as your mapview delegate and implement the method:
-(MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation
This is the method that actually produces the annotation view for the map view. In your case you might want to use an MKPinAnnotationView for your MKPointAnnotation!
You can check the protocol reference here.
EDIT: An example implementation of the method (that assumes that all of your MKPointAnnotations have the same purpose) could be:
-(MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation
{
static NSString *reuseId = #"MyReuseID";
MKPinAnnotationView * view = (MKPinAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:reuseId];
if (!view) {
view = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:reuseId];
//custom setup of your view, such as
//view.canShowCallout = YES;
}
return view;
}

Z-index of iOS MapKit user location annotation

I need to draw the current user annotation (the blue dot) on top of all other annotations. Right now it is getting drawn underneath my other annotations and getting hidden. I'd like to adjust the z-index of this annotation view (the blue dot) and bring it to the top, does anyone know how?
Update:
So I can now get a handle on the MKUserLocationView, but how do I bring it forward?
- (void) mapView:(MKMapView *)aMapView didAddAnnotationViews:(NSArray *)views {
for (MKAnnotationView *view in views) {
if ([[view annotation] isKindOfClass:[MKUserLocation class]]) {
// How do I bring this view to the front of all other annotations?
// [???? bringSubviewToFront:????];
}
}
}
Finally got it to work using the code listed below thanks to the help from Paul Tiarks. The problem I ran into is that the MKUserLocation annotation gets added to the map first before any others, so when you add the other annotations their order appears to be random and would still end up on top of the MKUserLocation annotation. To fix this I had to move all the other annotations to the back as well as move the MKUserLocation annotation to the front.
- (void) mapView:(MKMapView *)aMapView didAddAnnotationViews:(NSArray *)views
{
for (MKAnnotationView *view in views)
{
if ([[view annotation] isKindOfClass:[MKUserLocation class]])
{
[[view superview] bringSubviewToFront:view];
}
else
{
[[view superview] sendSubviewToBack:view];
}
}
}
Update: You may want to add the code below to ensure the blue dot is drawn on top when scrolling it off the viewable area of the map.
- (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated
{
for (NSObject *annotation in [mapView annotations])
{
if ([annotation isKindOfClass:[MKUserLocation class]])
{
NSLog(#"Bring blue location dot to front");
MKAnnotationView *view = [mapView viewForAnnotation:(MKUserLocation *)annotation];
[[view superview] bringSubviewToFront:view];
}
}
}
Another solution:
setup annotation view layer's zPosition (annotationView.layer.zPosition) in:
- (void)mapView:(MKMapView *)mapView didAddAnnotationViews:(NSArray *)views;
The official answer to that thread is wrong... using zPosition is indeed the best approach and fastest vs using regionDidChangeAnimated...
else you would suffer big performance impact with many annotations on map (as every change of frame would rescan all annotations). and been testing it...
so when creating the view of the annotation (or in didAddAnnotationViews) set :
self.layer.zPosition = -1; (below all others)
and as pointed out by yuf:
This makes the pin cover callouts from other pins – yuf Dec 5 '13 at 20:25
i.e. the annotation view will appear below other pins.
to fix, simply reput the zPosition to 0 when you have a selection
-(void) mapView:(MKMapView*)mapView didSelectAnnotationView:(MKAnnotationView*)view {
if ([view isKindOfClass:[MyCustomAnnotationView class]])
view.layer.zPosition = 0;
...
}
-(void) mapView:(MKMapView*)mapView didDeselectAnnotationView:(MKAnnotationView*)view {
if ([view isKindOfClass:[MyCustomAnnotationView class]])
view.layer.zPosition = -1;
...
}
Update for iOS 14
I know it's an old post, but the question is still applicable and you end up here when typing it into your favorite search engine.
Starting with iOS 14, Apple introduced a zPriority property to MKAnnotationView. You can use it to set up the z-index for your annotations using predefined constants or floats.
Also, Apple made it possible to finally create the view for the user location on our own and provided MKUserLocationView as a subclass of MKAnnotationView.
From the documentation for MKUserLocationView:
If you want to specify additional configuration, such as zPriority,
create this annotation view directly. To display the annotation view,
return the instance from mapView(_:viewFor:).
The following code snippet shows how this can be done (add the code to your MKMapViewDelegate):
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
// Alter the MKUserLocationView (iOS 14+)
if #available(iOS 14.0, *), annotation is MKUserLocation {
// Try to reuse the existing view that we create below
let reuseIdentifier = "userLocation"
if let existingView = mapView.dequeueReusableAnnotationView(withIdentifier: reuseIdentifier) {
return existingView
}
let view = MKUserLocationView(annotation: annotation, reuseIdentifier: reuseIdentifier)
view.zPriority = .max // Show user location above other annotations
view.isEnabled = false // Ignore touch events and do not show callout
return view
}
// Create views for other annotations or return nil to use the default representation
return nil
}
Note that per default, the user location annotation shows a callout when tapping on it. Now that the user location overlays your other annotations, you'd probably want to disable this, which is done in the code by setting .isEnabled to false.
Just use the .layer.anchorPointZ property.
Example:
func mapView(_ mapView: MKMapView, didAdd views: [MKAnnotationView]) {
views.forEach {
if let _ = $0.annotation as? MKUserLocation {
$0.layer.anchorPointZ = 0
} else {
$0.layer.anchorPointZ = 1
}
}
}
Here is there reference https://developer.apple.com/documentation/quartzcore/calayer/1410796-anchorpointz
Try, getting a reference to the user location annotation (perhaps in mapView: didAddAnnotationViews:) and then bring that view to the front of the mapView after all of your annotations have been added.
Swift 3:
internal func mapView(_ mapView: MKMapView, didAdd views: [MKAnnotationView]) {
for annotationView in views {
if annotationView.annotation is MKUserLocation {
annotationView.bringSubview(toFront: view)
return
}
annotationView.sendSubview(toBack: view)
}
}
Here is a way to do it using predicates. I think it should be faster
NSPredicate *userLocationPredicate = [NSPredicate predicateWithFormat:#"class == %#", [MKUserLocation class]];
NSArray* userLocation = [[self.mapView annotations] filteredArrayUsingPredicate:userLocationPredicate];
if([userLocation count]) {
NSLog(#"Bring blue location dot to front");
MKAnnotationView *view = [self.mapView viewForAnnotation:(MKUserLocation *)[userLocation objectAtIndex:0]];
[[view superview] bringSubviewToFront:view];
}
Using Underscore.m
_.array(mapView.annotations).
filter(^ BOOL (id<MKAnnotation> annotation) { return [annotation
isKindOfClass:[MKUserLocation class]]; })
.each(^ (id<MKAnnotation> annotation) { [[[mapView
viewForAnnotation:annotation] superview] bringSubviewToFront:[mapView
viewForAnnotation:annotation]]; });

how to show default user location and a customized annonation view in map kit?

I am using map kit and showing customized annotation view. One is carImage and the another one is userImage(as current location of user). Now I want to show current user location default which is provided by map kit.but unable to show it. How do I show blue circle+my car in map kit?
To show the user location, set the following property to true on the map view object
mapView.showsUserLocation = YES;
To display a custom annotation, set image property on the map view annotation
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation
{
// check for nil annotation, dequeue / reuse annotation
// to avoid over riding the user location default image ( blue dot )
if ( mapView.UserLocation == annotation ) {
return nil; // display default image
}
MKAnnotationView* pin = (MKAnnotationView*)
[mapView dequeueReusableAnnotationViewWithIdentifier: PIN_RECYCLE_ID];
if ( pin == nil ) {
pin = [(MKAnnotationView*) [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier: PIN_RECYCLE_ID] autorelease] ;
pin.canShowCallout = YES;
}
else {
[pin setAnnotation: annotation];
}
pin.image = [UIImage imageNamed:#"car-image.png"];
return pin;
}