Reverse Geocoding on MKMapView by dragging Pins - iphone

I have a subclass "Location" of NSManagedObject and this class conforms to MKAnnotation.
When I add a Location to a Map...the ReverseGeocoder starts. By clicking the pin I see in the callout View the address of the pin. So everything works fine.
Now my problem.
I am able to drag the pins. When I am finished dragging, than again the ReverseGeocoder starts to ask for location Data. But I still have the old address in the callout view, although the placemarks are updated in the Location object. After dragging again I can see the new address. (of course the new one isn't new anymore, because I dragged).
Here is my code for the dragging state:
- (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)annotationView didChangeDragState:(MKAnnotationViewDragState)newState fromOldState:(MKAnnotationViewDragState)oldState{
//if dragging ended
if (newState == MKAnnotationViewDragStateNone && oldState == MKAnnotationViewDragStateEnding) {
CLLocation *location = [[CLLocation alloc] initWithLatitude:annotationView.annotation.coordinate.latitude longitude:annotationView.annotation.coordinate.longitude];
[self geocodeLocation:location forAnnotation:annotationView.annotation];
[location release];
}
And the Code for my reverse geocoder delegate
- (void)reverseGeocoder:(MKReverseGeocoder*)geocoder didFindPlacemark:(MKPlacemark*)place
{
Location* theAnnotation = [self.map annotationForCoordinate:place.coordinate];
if (!theAnnotation)
return;
// Associate the placemark with the annotation.
theAnnotation.city = [place locality];
theAnnotation.zipcode = [place postalCode];
theAnnotation.street = [place thoroughfare];
DEBUGLOG(#"Place: %#", place);
// Add a More Info button to the annotation's view.
MKPinAnnotationView* view = (MKPinAnnotationView*)[self.map viewForAnnotation:theAnnotation];
if (view)
{
DEBUGLOG(#"SUBTITLE: %#", theAnnotation.subtitle);
view.leftCalloutAccessoryView = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
}
}
Any help would be wonderful. In the end the Maps App is best example for doing that by dragging.

I figured out.
Because of KVO I hust hace to set the subtitle of the Location object. So it gets displayed.
But because the subtilte is generated by
// Associate the placemark with the annotation.
theAnnotation.city = [place locality];
theAnnotation.zipcode = [place postalCode];
theAnnotation.street = [place thoroughfare];
I just had to do the following in my NSManagedObject Subclass.
- (void) setSubtitle:(NSString *)title{ //do nothing}

Related

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...
}

Userlocation bubble annotation changing to custom annotation

I am adding a custom annotation along with the user's current location default bubble annotation but the user location annotation is changing to the other custom location after sometime when not in focus on mapview.
My viewForAnnotation method is :
-(MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation
{
NSString* annotationidentifier = #"customview";
CustomAnnotationView *customannotationview = (CustomAnnotationView*) [self.mapview dequeueReusableAnnotationViewWithIdentifier:annotationidentifier];
if([annotation isKindOfClass:[MKUserLocation class]])
{
customannotationview = [[CustomAnnotationView alloc] initWithAnnotationWithImage:annotation reuseIdentifier:annotationidentifier annotationviewimage:[UIImage imageNamed:#"location pin28.png"]];
customannotationview.canShowCallout = YES;
return customannotationview;
}
else if(![annotation isKindOfClass:[MKUserLocation class]] && annotation != _mapview.userLocation && (annotation.coordinate.latitude != _locationmanager.location.coordinate.latitude && annotation.coordinate.longitude != _locationmanager.location.coordinate.longitude))
{
customannotationview = [[CustomAnnotationView alloc] initWithAnnotationWithImage:annotation reuseIdentifier:annotationidentifier annotationviewimage:[UIImage imageNamed:#"image1.png"]];
return customannotationview;
}
return customannotationview;
}
I have put conditions in the custom annotation but still after some time if userlocation is out of focus for somtime it changes to image1.png
I believe your issue is the fact that both your user annotation and location annotation use the same identifier. What happens is, when you scroll the map around, your annotations get reused (the same way a table would reuse cells), and eventually, one of your location annotations is used for your user annotation.
Try giving the annotations different identifiers, or, if you have a small number of locations, remove the reuse code, that should fix it.

iOS Core Location Set Pin to user location in viewDidLoad

I am attempting to set a map annotation to the user's current location. I am trying to set the pin in the viewDidLoad method, however because the method
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation
Has not been called yet, the lat and long are 0.000000. Is there a way to call this method in my viewDidLoad or any other solution that will make a pin appear at my beginning location when the application loads?
UPDATE, Added Annotation Code
CLLocationCoordinate2D theCoordinate;
theCoordinate.latitude = (_currentLocation.latitude);
theCoordinate.longitude = (_currentLocation.longitude);
NSLog(#"The Coordinate Value:");
NSLog(#"%f, %f",theCoordinate.latitude,theCoordinate.longitude);
DDAnnotation *annotation = [[[DDAnnotation alloc] initWithCoordinate:theCoordinate addressDictionary:nil] autorelease];
annotation.title = #"Drag to Move Pin";
annotation.subtitle = [NSString stringWithFormat:#"%f %f", annotation.coordinate.latitude, annotation.coordinate.longitude];
[self.mapView addAnnotation:annotation];
UPDATE 2
Still not working, code is in the didUpdateLocation Method
static BOOL annotationAdded = NO;
if (!annotationAdded) {
annotationAdded = YES;
CLLocationCoordinate2D theCoordinate;
theCoordinate.latitude = _currentLocation.latitude;
theCoordinate.longitude = _currentLocation.longitude;
//Sets Initial Point to Africa Because Method to obtain current Location
//Hasen't Fired when View Loads
theCoordinate.latitude = (mapView.userLocation.coordinate.latitude);
theCoordinate.longitude = (mapView.userLocation.coordinate.longitude);
NSLog(#"The Coordinate Value:");
NSLog(#"%f, %f",theCoordinate.latitude,theCoordinate.longitude);
DDAnnotation *annotation = [[[DDAnnotation alloc] initWithCoordinate:theCoordinate addressDictionary:nil] autorelease];
annotation.title = #"Drag to Move Pin";
annotation.subtitle = [NSString stringWithFormat:#"%f %f", annotation.coordinate.latitude, annotation.coordinate.longitude];
[self.mapView addAnnotation:annotation];
}
MKMapView automatically places an annotation of class MKUserLocation when you set mapView.showsUserLocation = YES.
You can replace the default view for this annotation to whatever default annotation view you want by doing this in mapView:viewForAnnotation::
- (MKAnnotationView *) mapView:(MKMapView *)theMapView viewForAnnotation:(id <MKAnnotation>)annotation {
if ([annotation isKindOfClass:[MKUserLocation class]]) {
// replace the following with code to generate and return custom user position annotation view
return customAnnotationView;
}
//*** other code ***//
}
Update:
If all you want to do is set a pin initially (once) at the user's location when the view loads, then you will have to wait until the phone can grab the data you need since that takes some time. Add your annotation in mapView:didUpdateUserLocation the first time it is called, and that should do the trick:
- (void) mapView:(MKMapView *)theMapView didUpdateUserLocation:(MKUserLocation *)userLocation {
static BOOL annotationAdded = NO;
if (!annotationAdded) {
annotationAdded = YES;
//*** add annotation to mapView ***//
}
}
Final Comment:
I would generally avoid setting a static pin at a users location the first time this method is called, however, and instead opt to just using the default standard blue dot. That is because the location services in the phone take time to find an accurate reading on the user's location, but in the interest of time it will send you location updates as soon as possible. This means that the first location update may not be very accurate, but subsequent updates may be much more accurate. That is why the standard blue dot sometimes changes position frequently within the first few moments of showing up on the map.
Just a caveat. Obviously what you choose to do depends on what the purpose of your app is.
I've never found a way to manually call that method. I believe it's a delegate method that's completely passive. Sorry.
It takes some time for the device to determine the location -- you can't speed up the process by calling -locationManager:didUpdateToLocation: yourself. You'll need to either use #Matt's suggestion to let the map draw the user's location, or else wait for -...didUpdateToLocation: to be called and take action then.

iphone - MKMapView callout doesn't show on iPhone 4 with iOS 4.1

I have a problem with MKMapView. I add annotations like that:
// set up new points
for(int i = 0; i < [_locations count]; i++) {
PPlace * place = [_locations objectAtIndex:i];
PlaceAnnotation * placeAnnotation = [[PlaceAnnotation alloc] initWithPlace:place];
// if annotation is for currently selected place
placeAnnotation.isCurrent = i == currentIndexPath.row;
[self.mapView addAnnotation:placeAnnotation];
if (placeAnnotation.isCurrent) {
[self.mapView selectAnnotation:placeAnnotation animated:YES];
}
[placeAnnotation release];
}
So I try to display callout bouble immediately after added, not after annotation pin is tapped.
Everything works fine in simulator, also on iPhone 3GS with iOS 4.3.2. However, the callouts do not show on iPhone 4 with iOS 4.1 (they show only after pin is tapped). Any idea how to solve this?
My guess is that you did not assign a value to the title property of your annotation class. Even though you may set canShowCallout to YES, the call out bubble will not show unless you have something in your title.
try adding
placeAnnotation.canShowCallout = YES;
so it looks like:
// set up new points
for(int i = 0; i < [_locations count]; i++) {
PPlace * place = [_locations objectAtIndex:i];
PlaceAnnotation * placeAnnotation = [[PlaceAnnotation alloc] initWithPlace:place];
// if annotation is for currently selected place
placeAnnotation.isCurrent = i == currentIndexPath.row;
[self.mapView addAnnotation:placeAnnotation];
if (placeAnnotation.isCurrent) {
[self.mapView selectAnnotation:placeAnnotation animated:YES];
placeAnnotation.canShowCallout = YES;
}
[placeAnnotation release];
}
hope this helps!
WeSaM
You will need to implement the following method:
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation
You can find the documentation here
You are calling it at the wrong time. You can't select it until after it has loaded.
Use the delegate method of the MKMapView:
- (void)mapView:(MKMapView *)mapView didAddAnnotationViews:(NSArray *)views
And from inside of that method call:
[yourMapView selectAnnotation: yourAnnotation animated: YES];

Trying to Show User Location on Mapkit, the most annoying thing ever. Anyone lend a hand?

I'm following this tutorial (http://icodeblog.com/2009/12/21/introduction-to-mapkit-in-iphone-os-3-0/) on adding mapkit and annotations to an application. However, i'm seriously struggling with the User Location. I'm new to xcode so not quite sure what to do next. I have tried Tony's option:
step one: add the CoreLocation framework to the project.
Step two: add this function to the iCodeMapViewController.m:
- (void)setCurrentLocation:(CLLocation *)location {
MKCoordinateRegion region = {{0.0f, 0.0f}, {0.0f, 0.0f}};
region.center = location.coordinate;
region.span.longitudeDelta = 0.15f;
region.span.latitudeDelta = 0.15f;
[self.mapView setRegion:region animated:YES];
}
step three: add this code to the ViewForAnnotation Method:
if (annotation != mapView.userLocation) {
//the rest of the ViewForAnnotation code goes here
}else{
CLLocation *location = [[CLLocation alloc]
initWithLatitude:annotation.coordinate.latitude
longitude:annotation.coordinate.longitude];
[self setCurrentLocation:location];
}
But when i go to build, it doesn't like it.
I've also tried this option:
-(MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id )annotation
{
if ([annotation isKindOfClass:MKUserLocation.class]) return nil;
//rest of code
}
The blue dot shows, my custom annotations show but the app crashes when i try and scroll through the table. The debugger gives no help but does stop on this statement.
Can someone please help? With code examples too? i think the answer to this post might be useful to a number of people also struggling with the mapkit.
Cheers
I had the same problem, but I managed to solve it.
In cellForRowAtIndexPath I did this:
NSMutableArray *annotations = [[NSMutableArray alloc] init];
if(indexPath.section == 0)
{
for(MinuAsukohad *annotation in [mapView annotations])
{
if(![annotation isKindOfClass:[MKUserLocation class]])
{
if([annotation annotationType] == MinuAsukohadTypeInterest)
{
[annotations addObject:annotation];
}
}
}
cell.textLabel.text = [[annotations objectAtIndex:indexPath.row] title];
}
You just have to repeat it for all the sections.
Sounds like you are trying to include your current location as one of the cells in the table ... look at your console and give us the output when the crash happens.