Adding UIButtonTypeDetailDisclosure - Logic issue - iphone

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.

Related

How do I know which callout has been clicked?

I'm having alot of trouble trying to identify which callout bubble has been clicked.
Is there a way of identifying the callout bubbles some how as I've tried a number of examples on here but because I'm quite new to Objective C I can't seem to work out how to use them.
The following answers I've been trying to use but I'm confused with them
How to track which annotation callout clicked
I'm looking at tagging the annotations but thats even confusing me. :(
Taken from the second answer in the above link:
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation
{
if ([annotation isKindOfClass:[MKUserLocation class]]){
return nil;
if(annotation.tag == 111)
//Do something
else
//Do some other thing
}
How does the first part of this snippet work, perhaps if I understood it better I'd know what I'm working with.
Also how would I go about retrieving the tags when the callout is clicked so I can make the app respond respectively.
You can subclass MKPinAnnotationView so you can identify it when
- (void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKPinAnnotationView *)view
is called.
You have to create class which extends MKAnnotation and create some property like tag and assign unique value or tag and add in mapview like.
Place* home = [[Place alloc] init] ;
home.name = [dForMap valueForKey:#"shortaddress"];
home.latitude = [[dForMap valueForKey:#"latitude"]floatValue];
home.longitude = [[dForMap valueForKey:#"longitude"]floatValue];
home.description = [dForMap valueForKey:#"shortaddress"];
home.flierid = count;
PlaceMark* from = [[PlaceMark alloc] initWithPlace:home] ;
from.flierid = count;
from.mapAnnotationType = MapAnnotationTypeProperty;
[self addAnnotation:from];
you can check the property in didSelectAnnotationView method like this way when callout bubble is clicked.
- (void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view
{
PlaceMark *a = (PlaceMark*)view.annotation;
Place *test = a.place;
int tag = test.flierid;
}
Thanks.

adding and removing MKAnnotation from a map view

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

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

iOS rotating MKAnnotationView in response of MKMapView rotation

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.

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.