In my application I have a MKMapView where several annotations are shown. The map rotates based on the heading of the device. To rotate the map the following statement is performed (called by the method locationManager: didUpdateHeading:)
self.navigationMapView.mapView.transform = CGAffineTransformMakeRotation(-heading);
where the heading (magnetic) is expressed in radians. What I noticed it's that even the annotations in the map rotate and I don't want it. I tried to fix it in the following method:
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation{
static NSString *identifier = #"AnnotationViewIdentifier";
MKAnnotationView *av = [mapView dequeueReusableAnnotationViewWithIdentifier:identifier];
if (av == nil) {
av = [[[MKPinAnnotationView alloc]initWithAnnotation:annotation reuseIdentifier:identifier] autorelease];
}
else{
av.annotation = annotation;
}
av.transform = CGAffineTransformMakeRotation(degreesToRadians(self.arController.currentHeading.magneticHeading));
av.canShowCallout = YES;
return av;
}
and I want to call this method from "didUpdateHeading:" but I really don't know how to do it. The TableView class has the reloadData function that calls the delegate method but here the things seem different. Any suggestions?!
Another question, my annotations on the map show the distance from the user, I would like to update them (distance label) as soon as the user change location. Any suggestions?!
So with a MKMapView having that be called properly is a little bit annoying. Essentially you have one of two options. Option 1: Create an array of the annotation on the screen and remove that from the map_view and then re-add them to the map_view. Essentially creating your own reload data function. Option 2: Do something simple such as
CGLocationCoordinate2D coordinate = map_view.center;
map_view.center = coordinate;
-- Essentially the point is to reset a property of the map causing it to redraw. However this option is not always going to work. Option 1 has a higher chance of working however that one can also fail, so if simply taking the annotations off and re-adding them causes nothing to happen then simply decreate the map and then recreate the map at the end of your map refresh function something like.
[my_map_view removeFromSuperView];
[my_map_view release];
my_map_view = nil;
my_map_view = [[MKMapView alloc] initWithFrame:CGRectMake(0,0,320,480)];
one of these options should work. I had to do option one for my solution however I know some people are lucky and option 2 works just as well.
Related
I have a custom class for my MKAnnotations, I want to override the default mapView:viewForAnnotatation method so that I can add extra information in the callout. When I set my delegate in code (as per the code below) the annotations are dropped on the map and are selectable but my mapView:viewForAnnoation is never called.
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation{
NSLog(#"viewForAnnotation: called");
MKAnnotationView *aView = [mapView dequeueReusableAnnotationViewWithIdentifier:#"mapPin"];
if(!aView){
aView = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"mapPin"];
}
aView.annotation = annotation;
return aView;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
self.mapView.delegate = self;
}
I know the delegate is being set as I can override the method -(void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view and I see an NSLog when I select an annotation.
When I change from setting the delegate in code to setting it in the Storyboard the method is called (NSLog(#"viewForAnnotation: called"); statements appear) but the annotations don't appear on the map and sometimes this error appears:
<Error>: ImageIO: CGImageReadSessionGetCachedImageBlockData *** CGImageReadSessionGetCachedImageBlockData: bad readSession [0x8618480]
This seems like two separate issues:
Regarding the setting delegate in code v storyboard, it's hard to reconcile your various observations (delegate method didSelectAnnotationView is getting called in both scenarios, but viewForAnnotation is not). The only difference between setting it in code v in the storyboard is the timing of when the delegate is getting set. You're not showing us the process of adding the annotations, so it's hard to diagnose on the basis of what you've described. If none of your delegate methods were getting called, I'd suspect the mapView IBOutlet, but if some are working and others aren't, I can only suspect a timing issue.
Regarding the setting of the MKAnnotationView, the default implementation does nothing. You either need to write your own subclass of MKAnnotationView, set its image if you're using your own image, or, much easier, just use MKPinAnnotationView. But just creating a MKAnnotationView won't do anything. You really want:
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation{
// If it's the user location, just return nil.
if ([annotation isKindOfClass:[MKUserLocation class]])
return nil;
// Handle any custom annotations.
MKAnnotationView *aView = [mapView dequeueReusableAnnotationViewWithIdentifier:#"mapPin"];
if(aView){
aView.annotation = annotation;
} else {
aView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"mapPin"];
}
aView.canShowCallout = NO;
return aView;
}
(Note, I'm not only creating a MKPinAnnotationView, but I'm also making sure that it's not a MKUserLocation, in case you ever choose to show user location on the map. I'm also going to explicitly set the canShowCallout, as that's presumably the reason you're writing this method at all.)
Bottom line, if you want to show a simple pin annotation view, use MKPinAnnotationView. Using MKAnnotationView alone will result in no annotations from appearing.
In case someone else is searching for a reason why mapView:viewForAnnotatation is not being called when the delegate is set in code, there is a bug in iOS 6 - http://openradar.appspot.com/12346693
I came across the same issue and I wanted to share my solution:
I also override
(MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation
But I realised that these Annotations work similar to TableView and iOS will reuse the annotations like the cells in TVC (table view controller)
Since I was only using one Identifier mapView dequeueReusableAnnotationViewWithIdentifier:#"mapPin" it would not need to call ViewForAnnotation again if it had enough 'annotation' in memory.
So my solution was to create multiple Identifier the first time the map load based on my conditions.
This solved my problem.
i just dropped to this question accidentally when i was searching for the same problem.
I solved my problem, in my case im resizing the mapview.
I added delegate after i resize the mapview. it works now perfectly.!
I have a map view with annotations, and these annotations display a callout. When the callout's disclosure detail button is clicked, it segues into a new view.
My MKAnnotations are a custom class that implements <MKAnnotation>. Let's call that class MyClass. They are stored in an NSMutableArray. During viewdidload of this view, I add each object of MyClass in this array to the map view's annotations. Using the debugger, I can see that once all of this adding is done, the [self.MapView annotations] order is the same as the NSMutableArray.
Now I set another breakpoint within mapView:viewForAnnotation: and check out the order of 1) my NSMutableArray and 2) [self.MapView annotations]. The array is of course in the same order. However, the order of the annotations has been scrambled.
This was a big problem for me, because I needed to use the specific instance of MyClass that the user selected in the next view. AKA, I wanted to look at the annotation, find its index, and then use that to get the same index within the array.
I've now realized that I can just save the annotation directly (coming from an Android background, this was very cool to me). However, I am still conceptually at a loss as to why the order became scrambled. Can someone help me? Code below:
- (void)viewDidLoad
{
if([fromString isEqualToString:#"FromList"])
self.navigationItem.hidesBackButton = TRUE;
else {
self.navigationItem.rightBarButtonItem = nil;
}
self.array = [MySingleton getArray];
//set up map
//declare latitude and longitude of map center
CLLocationCoordinate2D center;
center.latitude = 45;
center.longitude = 45;
//declare span of map (height and width in degrees)
MKCoordinateSpan span;
span.latitudeDelta = .4;
span.longitudeDelta = .4;
//add center and span to a region,
//adjust the region to fit in the mapview
//and assign to mapview region
MKCoordinateRegion region;
region.center = center;
region.span = span;
MapView.region = [MapView regionThatFits:region];
for(MyClass *t in self.array){
[MapView addAnnotation:t];
}
[super viewDidLoad];
}
//this is the required method implementation for MKMapView annotations
- (MKAnnotationView *) mapView:(MKMapView *)thisMapView
viewForAnnotation:(MyClass *)annotation
{
static NSString *identifier = #"MyIdentifier";
//the result of the call is being cast (MKPinAnnotationView *) to the correct
//view class or else the compiler complains
MKPinAnnotationView *annotationView = (MKPinAnnotationView *)[thisMapView
dequeueReusableAnnotationViewWithIdentifier:identifier];
if(annotationView == nil)
{
annotationView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:identifier];
}
annotationView.pinColor = MKPinAnnotationColorGreen;
//pin drops when it first appears
annotationView.animatesDrop=TRUE;
//tapping the pin produces a gray box which shows title and subtitle
annotationView.canShowCallout = YES;
UIButton *infoButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
annotationView.rightCalloutAccessoryView = infoButton;
return annotationView;
}
When you call addAnnotation or addAnnotations, the map view adds the reference(s) to its internal list of annotations.
The annotations property of MKMapView simply returns this internal list (whatever type it might be) as an NSArray.
I don't know of any place in the documentation where it states that the annotations property returns the array in the same order that you added the annotations in. If you have showsUserLocation turned on, the array will include that annotation even though you didn't explicitly add it.
You do not need to be concerned about nor should you depend on the order of the objects in the annotations property.
Just a few suggestions regarding the code:
Since your array contains objects that implement <MKAnnotation>, instead of looping through it, you can add all the annotations in one shot by calling addAnnotations (plural) and pass it the array
In viewForAnnotation, none of the properties you are setting depend on any specific annotation so you can set them all inside the if (av == nil) block. This way you get maximum reuse.
Also in viewForAnnotation, after and outside the if, you should set the annotation property of the view to the current annotation. This is in case the view is being reused from another annotation.
Finally, in viewForAnnotation, don't assume the annotation will be of type MyClass. If you turn on showsUserLocation, that won't be the case. It's safer to declare the parameter as id<MKAnnotation> and then if necessary check what its class is and then cast it.
#Anna, you state you should not be concerned for the order of the annotations. That's not true in my case. Some annotationviews might overlap, and I always need a specific one to be on the top of the two overlapping views. So the order DO makes sense for the annotations, as I hope the - (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation gets called in the same order as i added the annotations.
EDIT:
and the solution is here :-)
I'm trying to replace the blue 'currentLocation' dot with a custom annotation. That code is working well (I am just implementing viewForAnnotation and cycling through that, replacing the annotation ofClass MKUserLocation with a custom image).
However, once I replace the annotation, the user's current location stops updating. All related functions (like didUpdateUserLocation) thus stop being called. This causes a lot of problems. I tried implementing the various code blocks at MKUserLocation Custom View not moving! but I couldn't get it to work. After extensive Googling and stackoverflow searching, I've come up with nothing.
Does anyone have a solution to this problem?
Here is the code for the viewForAnnotation method:
_userDot is an instance variable within MapScreen.m (the place where all of this code is). It's alloced in the viewDidLoad and is of type MKAnnotationView. Basically I couldn't just get rid of the annotation, so I wanted to set it to an invisible image instead (currently 1x1 for debugging).
- (MKAnnotationView *)mapView:(MKMapView *)newMapView viewForAnnotation:(id <MKAnnotation>)annotation
{
if ([annotation isKindOfClass:[MKUserLocation class]]) {
//Initialize a new MKAnnotationView using the current userLocation annotation
//Then make it invisible (because we are cheap like that)
//TODO: Make it actually invisible (right now it is 1x1 for debugging)
if (_userDot)
{
_userDot.annotation = annotation;
_userDot.image = [UIImage imageNamed:#"mister_taco"];
_userDot.frame = CGRectMake(1, 1, 1, 1);
return _userDot;
}
else
return nil;
}
else {*/
return nil;
//}
}
Thanks,
Brian
Trying to figure out this MKPolygon working. I've created a whole heap MKMapPoints and placed them into an array. Then I make a Polygon out of them with:
[MKPolygon polygonWithPoints:pointArr count:sqlite3_column_int(countStatement, 0)];
and add that to an Array for retrieval later.
Later I loop through the array and add each object (MKPolygon) to the map with:
[mapView addOverlay:[overlays objectAtIndex:i]];
And this according to logs works fine.
I then implement mapView: viewForOverlay: like so
- (MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id <MKOverlay>)overlay {
MKOverlayView *theOverlayView = nil;
for (MKPolygon *theOverlay in overlays) {
if (theOverlay == overlay) {
MKPolygonView *thePolygonView = [[[MKPolygonView alloc] initWithPolygon:theOverlay] autorelease];
theOverlayView = thePolygonView;
}
}
return theOverlayView;
}
As far as I can see this should technically work. But it doesn't, the App just crashes when it gets to the region of the map that I think its on (points may be wrong because of lat long mixup or conversion mistake but thats another thing)
Am I missing some property I need to set or could the incorrect points be causing the crash? Or am I missing it entirely?
Cheers for any help.
This was actually correct but I made a mistake putting more than I remembered into overlays - it was actually and array of dictionaries which stored the MKPolygon and a string. Correcting this got it working.
Ok, so I’m having this problem. What I want to do is manually add multiple annotations to a map. When I add just one annotation, it works flawlessly. The pin drops, you can click on it to see its callout, life is good.
The problem comes when I want to add more than one. When I add the second, suddenly the pin’s aren’t coloured correctly (i.e. depending on their magnitude they should be a certain color, but they’re now both the same…), and more importantly when you click on them, to see their callout, the app crashes with exex_bad_access. I really have no idea what’s wrong, maybe I’m adding too many views to the map? But it’s only 9 pins and the pins themselves add just fine.
Here’s my code…
- (void)viewDidLoad {
[super viewDidLoad];
NSMutableArray *stops = [[NSMutableArray alloc] init]; //Get list of all the stops available
Bus *bus1 = [[Bus alloc] init]; // Bus 1 holds the stops
stops = [bus1 returnStops];
for (NSString *stop in stops) //Go through each stop to add annotation to map
{
Bus *bus2 = [bus1 initWithStop:stop]; //Create an instance of bus with a given stop
MapAnnotation *eqAnn = [MapAnnotation annotationWithBus:bus2];
[self.mapView addAnnotation:eqAnn]; //Add the annotation to the map
//[eqAnn release];
//[bus2 release];
}
[self recenterMap];
[stops release];
}
- (MKAnnotationView *)mapView:(MKMapView *)mapView
viewForAnnotation:(id <MKAnnotation>)annotation {
MKAnnotationView *view = nil;
if(annotation != mapView.userLocation) {
MapAnnotation *eqAnn = (MapAnnotation*)annotation;
view = [self.mapView dequeueReusableAnnotationViewWithIdentifier:#"busLoc"];
if(nil == view) {
view = [[[MKPinAnnotationView alloc] initWithAnnotation:eqAnn
reuseIdentifier:#"busLoc"] autorelease];
}
CGFloat magnituide = [eqAnn.bus.magnitude floatValue];
if(magnituide >= .80f) {
[(MKPinAnnotationView *)view setPinColor:MKPinAnnotationColorRed];
} else if(magnituide >= .60f) {
[(MKPinAnnotationView *)view setPinColor:MKPinAnnotationColorPurple];
} else
{
[(MKPinAnnotationView *)view setPinColor:MKPinAnnotationColorGreen];
}
[(MKPinAnnotationView *)view setAnimatesDrop:YES];
[view setCanShowCallout:YES];
}
return view;
}
even tried removing the second function, but it didn’t do anything.
Thanks for the help!
P.S I should also add, there’s usually one or two pins out of the 9 which works when you click the annotation…
If i even try to manually just two annotations by hand in the program (i.e., remove the loop), it still fails and the color is still wrong...
It would appear that your memory management of the stops variable is incorrect. You allocate a mutable array, then replace that array with the return value of -[Bus returnStops], then release that. Also it's not clear what's going on with bus2 - does -[Bus initWithStop:] return a different instance of Bus? It's not usual to send any method -init* on an already-initialised object. I think that you probably are confused by the memory management conventions in Cocoa Touch. Here's a collection of articles and other references on Cocoa memory management (which is the same beast).
Have you tried using AddAnnotations instead of add annotation? - (void)addAnnotations:(NSArray *)annotations. This might work for you...but looking at the answer above and further inspection you are having some memory managment issues in your viewDidLoad (though thi s might not be the cause of your problem, but it could be). First of you are allocating the array (stops) and then ovveriding it with some array in the Bus object, this will cause a leak. Also you are then releasing that array which might be causing the crash since you are releasing the array that is actually in the Bus object w ithout having increased a reference count to it. I am not sure what initWithStop is doing but you might be getting a leak here too if initWithStop retains the object.
I wouldn't call it a memory management problem -- I'd just say you are using array references incorrectly.
After constructing the array with NSMutableArray *stops = [[NSMutableArray alloc] init], the next step is to use [stops addObject: ] to add each stop you want to store.
After that? It's not clear what you are really trying to do.
SO the answer was that I kept sending bus1 the init object, so it got confused.
"Hi David,
Your data model looks hosed to me. You only have one bus object that you are repeatedly sending initWithStop: to.
Hope this helps.
Good luck!
"
Thank you guys for your help! You all helped me quite a bit!