adding and removing MKAnnotation from a map view - iphone

i have an app where i use MapView and i have 4 types of MKAnnotation. i have also 4 buttons on the screen. each button should show or hide one of the MKannotation types.
i'm able to track the types and remove them.but when i try to add any MKannotation i get an error message. after searching i found a similar problem which has not been answered.
An instance 0x6ec5750 of class MapPoint was deallocated while key value observers were still registered with it. Observation info was leaked, and may even become mistakenly attached to some other object.
ios5 removing annotation from a mapview
first of all i'm adding the MKAnnotation from after calling a web service:
for (int x=0; x<PromotionsArray.count; x++)
{
Promotion *pr = [PromotionsArray objectAtIndex:x];
CLLocationCoordinate2D newCoord ={ pr.promotionLatitude, pr.promotionLongitude};
MapPoint *mp= [[MapPoint alloc] initWithCoordinate:newCoord];
mp.currentTitle=pr.PromotionTitle;
mp.currentSubTitle=pr.RetailerName;
mp.currentPromotionArrayID=[NSString stringWithFormat:#"%d",x];
mp.currentRetailerID = pr.RetailerID;
mp.currentPromotionType = pr.PromotionType;
[mapView addAnnotation:mp];
}
now i have 4 buttons on the man view. Type1,Type2,Type3 and Type4 button.
if i click on Type1 button it will remove all the MKAnnotation of Type1, using the following code which works perfectly:
for (id <MKAnnotation> myAnnot in [mapView annotations])
{
if (![myAnnot isKindOfClass:[MKUserLocation class]])
{
if([(MapPoint *)myAnnot currentPromotionType]==PromotionType1)
{
NSLog(#"Hiding All Type1 Annotations");
[mapView removeAnnotation:myAnnot];
}
}
}
now if i want to show the Type1 again, i use the following code which cause the problem:
for (int x=0; x<PromotionsArray.count; x++)
{
Promotion *pr = [PromotionsArray objectAtIndex:x];
if (pr.promotionType==PromotionType1)
{
CLLocationCoordinate2D newCoord ={ [pr.promotionLatitude doubleValue],[pr.promotionLongitude doubleValue]};
MapPoint *map= [[MapPoint alloc] initWithCoordinate:newCoord];
map.currentTitle=pr.PromotionTitle;
map.currentSubTitle=pr.RetailerName;
map.currentPromotionArrayID=[NSString stringWithFormat:#"%d",x];
[mapView addAnnotation:map];
}
}
the problem appears in this line
[mapView addAnnotation:map];
which causes the error message i mentioned relier(here is it again)
An instance 0x6ec5750 of class MapPoint was deallocated while key value observers were still registered with it. Observation info was leaked, and may even become mistakenly attached to some other object.

works now, the problem is in totally another method.
i was trying to convert double value to NSString by doing this:
[Nsstring stringWithFormat:#"%d",doubleVal];
the %d should be %f !!!!! that was a stupid small mistake.
thanks all for the help and #AnnaKarenina for your hint

// REMOVING ALL ANNOTATION
for (id <MKAnnotation> myAnnot in [objMapView annotations])
{
if (![myAnnot isKindOfClass:[MKUserLocation class]])
{
[objMapView removeAnnotation:myAnnot];
}
}

Related

Get variable or object from IBAction method

I have been reading, googling and watching Lynda videos to find the answer for this the last couple days. I haven't found a good answer yet.
This seems like it should be pretty simple. With normal methods I can pass variables. But with IBAction being (void) I cant figure out how to get a variable to another method.
Here are some simple examples of what I would like to do:
- (IBAction)treeButton:(id)sender {
int test = 10;
}
-(void)myMethod{
NSLog(#"the value of test is %i",test);
}
This what I really want to have work. I am try to have a button set the initial location that I want to store and use in another method.
- (IBAction)locationButton:(id)sender {
CLLocation *loc1 = [[CLLocation alloc]
initWithLatitude:_locationManager.location.coordinate.latitude
longitude:_locationManager.location.coordinate.longitude];
}
-(void)myMethod{
NSLog(#"the value of test is %i",test);
NSLog(#"location 1 is %#",loc1);
}
Any suggestions to lead me in the right direction would be great. I have read and watched videos on variable scope, instance varaibles etc. Just not understanding what I need to do here
Change myMethod to accept the parameters you need:
- (void)myMethod:(CLLocation *)location {
NSLog(#"location 1 is %#", location);
}
Invoke it something like so:
- (IBAction)locationButton:(id)sender {
CLLocation *loc1 = [[CLLocation alloc]
initWithLatitude:_locationManager.location.coordinate.latitude
longitude:_locationManager.location.coordinate.longitude];
[self myMethod:loc1];
}
If you need it to be accessible by multiple methods or at different points in the code, I recommend creating an instance variable for loc1 in your #interface declaration.
#interface MyClass : NSObject {
CLLocation *loc1;
}
In your method, instead of re-declaring it, you'd just set it:
loc1 = [[CLLocation alloc]
initWithLatitude:_locationManager.location.coordinate.latitude
longitude:_locationManager.location.coordinate.longitude];
In myMethod, just access it:
- (void)myMethod{
NSLog(#"location 1 is %#", loc1);
}

MKMapView annotations changing/losing order?

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 :-)

Adding UIButtonTypeDetailDisclosure - Logic issue

I have added a annotaion pin point to my application, and when the user clicks on the pin, the popup appears with the blue arrow on it (UIButtonTypeDetailDisclosure). Now what i want to do is, when the user clicks on it i need the view to redirect to another view with the more details about it.
for example: If its a Hospital that i click, and the popup will give its name and the UIButtonTypeDetailDisclosure button. When i click on the UIButtonTypeDetailDisclosure button, i should redirect to another view whcih gives more information about that hospital.
My code:
-(void)callingMap:(NSArray *)hospitalArray {
for(Hospital *hospital in hospitalArray)
{
MKCoordinateRegion region = { {0.0, 0.0 }, { 0.0, 0.0 } };
region.center.latitude = hospital.lati;
region.center.longitude = hospital.longi;
[self.mapView setRegion:region animated:YES];
DetailMap *hospitalInfo = [[DetailMap alloc] init];
hospitalInfo.title = [hospital name];
hospitalInfo.subtitle = [hospital address];
hospitalInfo.coordinate = region.center;
[self.mapView addAnnotation:hospitalInfo];
}
}
}
-(MKAnnotationView *)mapView:(MKMapView *)mV viewForAnnotation:
(id <MKAnnotation>)annotation {
MKAnnotationView* ann = nil;
if(annotation != mapView.userLocation)
{
ann = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"try"];
ann.canShowCallout = YES;
ann.image = [UIImage imageNamed:#"arrow.png"];
UIButton* butt = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
[butt addTarget:self action:#selector(displayArrow:) forControlEvents:
UIControlEventTouchUpInside];
ann.rightCalloutAccessoryView = butt;
}
return ann;
}
-(void)displayArrow:(id)sender{
MoreHospitalDetailViewController *mhdvc = [[MoreHospitalDetailViewController alloc] initWithNibName:nil bundle:nil];
mhdvc.hospitalArray = ????????
// in the MoreHospitalDetailViewController i have an Array called hospitalArray. I need to insert the selected hospital object (after clicking the particular arrow) to this array and pass it to the next view. How to do it ? //
[self.navigationController pushViewController:mhdvc animated:YES];
}
In the MoreHospitalDetailViewController i have an Array called hospitalArray. I need to insert the selected hospital object (after clicking the particular arrow) to this array and pass it to the next view. How to do it ?
note: There can be many items (annotations) in the map, so how do i know which hospital Object i clicked when i am in the (void)displayArrow:(id)sender method ?
Help i am lost
In the displayArrow: method, you can get a reference to the annotation that was clicked using:
DetailMap *hospitalInfo
= (DetailMap *)[mapView.selectedAnnotations objectAtIndex:0];
Note you can also use the map view's calloutAccessoryControlTapped delegate method instead of doing addTarget and writing a custom method. For example:
-(void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view
calloutAccessoryControlTapped:(UIControl *)control
{
DetailMap *hospitalInfo = (DetailMap *)view.annotation;
//do something with the annotation...
}
You have two classes:
Hospital which contains the full details of a hospital including phone#, etc.
DetailMap which implements MKAnnotation and contains just a hospital's coordinate, name, and address (but not the phone# or anything else)
When tapping the callout button of an annotation, you want to get access to the full hospital info (not just coordinate, name, and address).
First, when you say in the comments "DetailMap which only contains 2 records", you really mean "fields" or "properties" instead of "records" (each hospital object is a "record").
Here are a couple of ways to solve this:
In DetailMap, add a property called say hospital of type Hospital and set it when creating DetailMap objects in the callingMap method (hospitalInfo.hospital = hospital). Then in the displayArrow: method, you can add that property to the array (mhdvc.hospitalArray = [NSArray arrayWithObject:hospitalInfo.hospital];).
Change the Hospital class so that it implements MKAnnotation itself and eliminate the DetailMap class entirely. Then you can directly pass Hospital objects to addAnnotation and in the displayArrow: method you would do this:
Hospital *hospital = (Hospital *)[mapView.selectedAnnotations objectAtIndex:0];
mhdvc.hospitalArray = [NSArray arrayWithObject:hospital];
The first option is easier since you've already created the DetailMap class but the second one is cleaner.
Still, the MoreHospitalDetailViewController doesn't need an array to pass the single hospital object (which contains multiple fields). You could just declare a Hospital *hospital property in MoreHospitalDetailViewController to pass the single object.

Problem with selected annotations

:)
I have a really strange problem when trying to retrieve the properties of a selected annotation. Here is the short description of my problem:
I pass the first object of the selected annotations array to a new array as it's the only one I need (acc. to Apple doc, passing the selectedAnnotations array to a new array only selects the first object. But I did tried to pull the object directly from the selectedAnnotations array at index path 0 and it's the same problem).
Then I transform the object into a Custom annotation object (as this is what the object should be).
Afterwards I try to access the properties of my custom annotation temp object. Here is when everything breaks loose. NSLog of the object only shows memory address. Text property is null. So basically I can't access it.
I would appreciate any help on what am I doing wrong or what approach should I use.
Thank you kindly!
Here is the code:
-(void) mapView:(MKMapView *)aMapView didSelectAnnotationView:(MKAnnotationView *)view
{
if ([view isUserInteractionEnabled])
// NSLog(#"Tapped!!!");
{
NSArray* selectedAnnotation=mapView.selectedAnnotations;
CustomAnnotations *selectedAnn=[selectedAnnotation objectAtIndex:0];
NSLog(#"selected annotation text is %#", selectedAnn.text);
My custom annotation class has a coordinate and a text property and it's being placed on map with this code:
CustomAnnotations* commentAnnotation = [[[CustomAnnotations alloc] initWithLocation:mapView.userLocation.location.coordinate andTitle:#"comment" andText:text]autorelease];
[mapView addAnnotation:commentAnnotation];
Furthermore, the view for annotation has the following coding:
-(MKAnnotationView *) mapView:(MKMapView *) aMapView viewForAnnotation:(id<MKAnnotation>)annotation
{
if ([annotation isKindOfClass:[MKUserLocation class]])
return nil;
MKAnnotationView *customAnnotation = (MKAnnotationView*)[mapView dequeueReusableAnnotationViewWithIdentifier:#"pin"];
if(!customAnnotation)
{
customAnnotation = [[[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"pin"]autorelease];
}
customAnnotation.centerOffset=CGPointMake(10, -30);
if ([annotation title]==#"comment")
{
customAnnotation.userInteractionEnabled=YES;
customAnnotation.image=[UIImage imageNamed:#"NewCommentsPin.png"];
}
return customAnnotation;
}
Any help would be much appreciated!
I figured out the problem: My custom annotation class was releasing the text in dealloc.
I still have a long way to go until I understand when to release and when not to but one step at a time!:)
Here is your release cheat sheet.
When you create an object, and it has any of the words new, alloc,
copy or retain in the constructor then you own it and you have
to release it if you do not need the reference any more.

iPhone Dev = maps and deselecting annotations

I am successfully drawing annotations on a map using an array of annotations. I can even click on the annotation and change it’s colour or image. My problem arises when the use selects the second annotation and I want to dynamically change the colour or image of the first one back to a non-selected colour/image. I can get the array of all the annotations and work through the array but once I try to set the colour or image ot the array I get a similar error.
for (MKAnnotationView *ann in map.selectedAnnotations){
if ([ann isMemberOfClass:[Place class]]) {
place = (Place *)ann;
if (currentPlaceID != place.placeID) {
UIImage *i = [UIImage imageNamed:#"pin.png"];
ann.image = i;
}
}
the above code works ok until I get to ann.image = i; then it errors. The errors I get are:-
-[Place setImage:]: unrecognized selector sent to instance 0x4514370 Terminating app due to uncaught exception
'NSInvalidArgumentException', reason: '** -[Place setImage:]:
unrecognized selector sent to instance 0x4514370'
Please advise as I have been going around in circles on this one for 2 days now!!!!
Any ideas on how best to do this?
thanks in advance
Do you have a property on the class Place called image?
Something like... #property (nonatomic, retain) UIImage* image; and is it properly synthesized? #synthesize image;?
The error is pretty straight forward, some object is receiving a message that it doesn't respond to, namely 'setImage' which is invoked by the .image.
Here is your code:
1. for (MKAnnotationView *ann in map.selectedAnnotations) {
2. if ([ann isMemberOfClass:[Place class]]) {
3. place = (Place *)ann;
4. if (currentPlaceID != place.placeID) {
5. UIImage *i = [UIImage imageNamed:#"pin.png"];
6. ann.image = i;
7. }
8. }
9. }
What I can see:
ann is an MKAnnotationView (from map.selectedAnnotations)
you are typecasting your annotation to a place on line 3 (is this right? Does Place subclass MKAnnotationView?)
you are properly setting the image to the annotation
What this means:
If Place is indeed a subclass of MKAnnotationView, you hid the setImage (somehow) method
If Place is NOT a subclass of MKAnnotationView, you've added an invalid annotation to the annotations (sure) that you're trying to treat as an annotation.
I finally figured out how to do this. As usual it's not that hard once you know how. Just thought I would pass this on.
- (void)setSelected:(BOOL)selected animated:(BOOL)animated
{
[super setSelected:selected animated:animated];
NSLog(#"here I am in set selected");
if (YES == selected)
{
NSLog(#"I am selected");
}
else
{
self.backgroundColor = [UIColor clearColor];
NSLog(#"not selected");
}
}