iphone mkannotationview strange problem - possibly being reused - iphone

I've just found a strange issue with my sub classed mkannotationview.
When I add the first 5 markers, they all work perfectly. In the mkannotationview sub class, I NSLog a message which I see 5 times. However, when I remove ALL the markers and redraw them - using all the same methods, I see the NSLog only once.
It's like the map is reusing existing annotationviews? Is there a way to force it to use new ones each time?
[UPDATE with code]
So the reason I cannot reuse (and this may or may not be the problem) is that I am creating unique markers with a label. The label on the marker contains a reference to the individual marker (consider it like a product ID)
So... in ProductPlot.h
#interface ProductPlot : NSObject <MKAnnotation> {
NSString *productID;
float latitude;
float longitude;
}
#property (nonatomic, copy) NSString *productID;
and ProductPlot.m
#implementation ProductPlot
#synthesize productID;
- (CLLocationCoordinate2D)coordinate {
CLLocationCoordinate2D coord = {self.latitude, self.longitude};
return coord;
}
- (NSString *) productID {
return productID;
}
then I have the annotation view sub classed as ProductPlotView.h
#interface ProductPlotView : MKAnnotationView {
ProductPlot *product;
}
and in ProductPlotView.m
#implementation ProductPlotView
- (id)initWithAnnotation:(id <MKAnnotation>)annotation reuseIdentifier:(NSString *)reuseIdentifier {
if(self = [super initWithAnnotation:annotation reuseIdentifier:reuseIdentifier]) {
product = (ProductPlot *)annotation;
UILabel *plate2 = [[[UILabel alloc] init] autorelease];
plate2.text = product.productID;
plate2.frame = CGRectMake(35, 4, 100, 30);
plate2.backgroundColor = [UIColor clearColor]; //]clearColor];
[plate2 setFont: [UIFont fontWithName: #"myFont" size: plate2.font.pointSize]];
[self addSubview:plate2];
}
self.frame = CGRectMake(0,0,133,40);
return self;
}
So then in my code, I plot the points using
- (void)plotPoint: (int) y latitude: (double) lat longitude: (double) lng productID: (NSString *) pID {
ProductPlot *newAnnotation = [[ProductPlot alloc] init];
newAnnotation.latitude = lat;
newAnnotation.longitude = lng;
newAnnotation.productID = pID;
[mapView addAnnotation:newAnnotation];
[newAnnotation release];
}
I also have the code to handle the annotations.
- (MKAnnotationView *)mapView:(MKMapView *)lmapView viewForAnnotation:(id <MKAnnotation>)annotation {
ProductPlotView *eventView = (ProductPlotView *)[lmapView
dequeueReusableAnnotationViewWithIdentifier:#"eventview"];
if(eventView == nil) {
eventView = [[[VehicleViewInfo alloc] initWithAnnotation:annotation
reuseIdentifier:#"eventview"]
autorelease];
}
eventView.annotation = annotation;
return eventView;
}
So... the above will take the productID and place it on a label which is the map marker. This seems to work perfectly on the FIRST plot instance, but if I remove the markers (using [mapView removeAnnotations:mapView.annotations];) then call this function again, it draws the points OK, but the product IDs are not correct. It appears to draw them at random.
NOTE: the code above has had a few
parts removed, so there may be typos
Thanks for any info.

To answer my own question, I added MKAnnotationView* annotationView = nil; to the top of my - (MKAnnotationView *)mapView:(MKMapView *)lmapView viewForAnnotation:(id <MKAnnotation>)annotation code.
Now it seems to be working perfectly. I'd still be keen to see if others think that this is the correct way to do this?

I solved a similar problem checking subviews on annotation view.
if it hasn't subviews i create a new label and add it as subview, if it has subviews i take the first and edit the label.text property.
In this way subviews are always zero or only one.

Related

User location image pin disappears most of time

i Am using following code
-(MKAnnotationView *) mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation
{
if ([[annotation title] isEqualToString:#"Current Location"] )
{
MKAnnotationView *anView = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"currentPin"];
anView.image = [UIImage imageNamed:#"pin_green.png"];
anView.canShowCallout = true;
anView.enabled = true;
return anView;
}
The issue is, it randomly disappears and appears again. Giving a very bad user experience. Any way to fix this?
There are several suspect things about this code:
You're not using dequeue, as someone has pointed out. In particular, the problem here is that you are making a new view every single time, rather than checking to see whether a new view needs making.
You are forgetting the key step, namely, to associate the view with the annotation.
Here is the canonical structure of a simple viewForAnnotation: implementation where we supply our own view:
- (MKAnnotationView *)mapView:(MKMapView *)mapView
viewForAnnotation:(id <MKAnnotation>)annotation {
MKAnnotationView* v = nil;
if ([annotation.title isEqualToString:#"Current Location"]) {
static NSString* ident = #"greenPin";
v = [mapView dequeueReusableAnnotationViewWithIdentifier:ident];
if (v == nil) {
v = [[MKAnnotationView alloc] initWithAnnotation:annotation
reuseIdentifier:ident];
v.image = [UIImage imageNamed:#"pin_green.png"];
v.canShowCallout = YES;
}
v.annotation = annotation;
}
return v;
}
Since that code works for me, I'd suggest you start with it and tweak it as necessary.
By the way, you do NOT need this method just to get a green pin! You do know that, right? iOS will give you a green pin (MKPinAnnotationColorGreen).
You should use MKMapView's dequeueReusableAnnotationViewWithIdentifier: and see if you get a view back before creating a new one with initWithAnnotation:reuseIdentifier::
MKAnnotationView *anView = [mapView dequeueReusableAnnotationViewWithIdentifier:#"currentPin"];
if (!anView) {
anView = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"currentPin"];
anView.image = [UIImage imageNamed:#"pin_green.png"];
anView.canShowCallout = true;
anView.enabled = true;
}
return anView;
That said, I'm not entirely sure this is the cause of your problem.

ios6 MKMapView deselection bug

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.

MKPolygon ontouch detailview

I've created a MKMapView with MKPolygons based on coordinates. There are multiple polygons on the map (look here for an example of what I am re-creating as an app).
What I am trying to do is when the user touches the polygon, it opens a popover view with information about the location. This information is currently stored inside a plist file with the coordinates.
What I currently have so far is that I am able to get touch event and print to the log that the polygon was touched.
The question that I have is:
Can MKPolygonView be used like an MKAnnotationView where once the user taps the pin more information pops up about that current location?
I want to do the same for the polygon view. When touched, the user would see more information about the location that is stored in the plist. If it is possible what would be the best way to get it to work?
My current code is below.
#import "outagemapViewController.h"
#import "MyAnnotation.h"
#import "WildcardGestureRecognizer.h"
#define METERS_PER_MILE 46309.344
#interface outagemapViewController ()
#end
#implementation outagemapViewController
- (void)viewDidLoad {
outages = [[NSArray alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:#"outages"ofType:#"plist"]];
for (NSDictionary *coloredAreas in outages) {
coordinateData = coloredAreas[#"coords"];
test = coloredAreas[#"outages"];
NSLog(#"test %#", test);
coordsLen = [coordinateData count];
NSLog(#"coords %d", coordsLen);
CLLocationCoordinate2D coords[coordsLen];
for (i=0; i < coordsLen; i++) {
NSString *lat = coordinateData[i];
NSArray *latt = [lat componentsSeparatedByString:#","];
double latitude = [[latt objectAtIndex:0] doubleValue];
double longitude = [[latt objectAtIndex:1] doubleValue];
coords[i] = CLLocationCoordinate2DMake(latitude, longitude);
}
MKPolygon* poly2 = [MKPolygon polygonWithCoordinates:coords count:coordsLen];
poly2.title=#"test";
[self.mapView addOverlay:poly2];
}
}
- (MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id <MKOverlay>)overlay {
{
if ([overlay isKindOfClass:[MKPolygon class]])
{
MKPolygonView* aView = [[MKPolygonView alloc] initWithPolygon:(MKPolygon*)overlay];
int numbers = [test intValue];
if(numbers >= 10){
aView.fillColor = [[UIColor greenColor] colorWithAlphaComponent:0.6];
aView.strokeColor = [[UIColor greenColor] colorWithAlphaComponent:1.0];
aView.lineWidth = 3;
}else if(numbers < 10){
aView.fillColor = [[UIColor yellowColor] colorWithAlphaComponent:0.6];
aView.strokeColor = [[UIColor yellowColor] colorWithAlphaComponent:1.0];
aView.lineWidth = 3;
}
return aView;
}
return nil;
}
}
-(void)viewWillAppear:(BOOL)animated{
CLLocationCoordinate2D zoomLocation;
zoomLocation.latitude = 35.20418;
zoomLocation.longitude = -89.86862;
MKCoordinateRegion viewRegion = MKCoordinateRegionMakeWithDistance(zoomLocation, 0.5*METERS_PER_MILE, 0.5*METERS_PER_MILE);
[_mapView setRegion:viewRegion animated:YES];
WildcardGestureRecognizer * tapInterceptor = [[WildcardGestureRecognizer alloc] init];
tapInterceptor.touchesBeganCallback = ^(NSSet * touches, UIEvent * event) {
UITouch *touch = [touches anyObject];
CGPoint point = [touch locationInView:self.mapView];
CLLocationCoordinate2D coord = [self.mapView convertPoint:point toCoordinateFromView:self.mapView];
MKMapPoint mapPoint = MKMapPointForCoordinate(coord);
for (id overlay in self.mapView.overlays)
{
if ([overlay isKindOfClass:[MKPolygon class]])
{
MKPolygon *poly = (MKPolygon*) overlay;
id view = [self.mapView viewForOverlay:poly];
if ([view isKindOfClass:[MKPolygonView class]])
{
MKPolygonView *polyView = (MKPolygonView*) view;
CGPoint polygonViewPoint = [polyView pointForMapPoint:mapPoint];
BOOL mapCoordinateIsInPolygon = CGPathContainsPoint(polyView.path, NULL, polygonViewPoint, NO);
if (mapCoordinateIsInPolygon) {
// debug(#"hit!");
NSLog(#"hit");
} else {
NSLog(#"miss");
}
}
}
}
};
[self.mapView addGestureRecognizer:tapInterceptor];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
Unfortunately, for overlays, there's no built-in touch-detection and callout view like there is for annotations.
You'll have to do the touch-detection manually like you're already doing (and it looks like it should work).
(Even more unfortunate here is that adding a gesture recognizer directly to the overlay view doesn't work -- you have to add it to the whole map and then check whether the touch point is in any overlay.)
For an overlay callout view, once you've detected a touch on an overlay, you can create a custom UIView and do addSubview. I suggest adding it to the map instead of the overlay view and you might be able to use the CGPoint point you are already calculating to determine the frame of the custom callout view.
You might also want to keep a ivar/property reference to the overlay callout view so it can be easily removed and re-added if the user taps on another overlay while the callout for another overlay is already displayed.
Another option which is probably easier is to create a custom UIViewController and present or push it. The specifics of showing it depend on whether you're using a navigation controller and/or storyboard.
If your app is also built for iPad, you could also show the "callout" using a UIPopoverController.
See How do I display a UIPopoverView as a annotation to the map view? (iPad) for a code example (it's with an annotation but you should be able to adapt it for the overlay).
Once you've identified which overlay was tapped, you need to display its associated data which is in your original data source (the outages array). Right now, overlays are created and added but have no reference back to the original data object (outage dictionary in outages array).
(Subclassing MKPolygon to add a custom property has issues and workarounds and creating a completely custom MKOverlay class introduces a lot of other additional work.)
For your current data source structure, a simple, quick (and somewhat crude) option is to set the overlay's title property to the index in the outages array of the outage object associated with the overlay. Since the title property is an NSString and the array index is an integer, we'll convert it to a string:
NSUInteger outageIndex = [outages indexOfObject:coloredAreas];
poly2.title = [NSString stringWithFormat:#"%d", outageIndex];
[self.mapView addOverlay:poly2];
In viewForOverlay, it looks like you're using test (which comes from an outage object) to determine the polygon's color. The value of the externally declared/set test variable will not necessarily be in sync with the overlay the delegate method is currently being called for (the map could call viewForOverlay multiple times for the same overlay and not necessarily in the order you add them). You have to retrieve the outage object based on some property of the overlay parameter. Since we are setting the overlay's title property to the outage's index:
//int numbers = [test intValue]; <-- remove this line
int outageIndex = [overlay.title intValue];
NSDictionary *outageDict = [outages objectAtIndex:outageIndex];
id outageNumbersObject = outageDict[#"outages"];
//replace id above with actual type
//can't tell from code in question whether it's NSString or NSNumber
int numbers = [outageNumbersObject intValue];
//use "numbers" to set polygon color...
Finally, when an overlay is tapped, you use the same method as in viewForOverlay to get the outage object:
if (mapCoordinateIsInPolygon) {
int outageIndex = [overlay.title intValue];
NSDictionary *outageDict = [outages objectAtIndex:outageIndex];
NSLog(#"hit, outageDict = %#", outageDict);
//show view with info from outageDict...
}

Centralized the code

- (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!

iPhone MapKit - update annotations coordinates and map

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];