I have a forward geocode block. like this:
[_geoCoder geocodeAddressString:searchString completionHandler:^(NSArray *placemarks, NSError *error) {....}];
As is, the geocode data stores in the NSArray * placemarks. Now I would like to do some annotation to the map, The addAnnotation method requires a MKPlacemark, so how do I convert the place mark in NSArrayinto a MKPlacemark? Thanks.
Here's what you want to do: Iterate through the parameters array - meaning, fetch each item from the array. As we're fetching the items, we want to create MKPlacemark objects using the data the items contain.
The Objective-C language gives us a special tool that allows us to iterate through the array - the "forin" loop:
for (CLPlacemark *placemark in placemarks)
{
// insert code here
}
Now, we want to create an MKPlacemark object from "placemark": (Note: An MKPlacemark object is a CLPlacemark object)
MKPlacemark *mkPlacemark = [MKPlacemark initWithCoordinate:(CLLocationCoordinate2D)coordinate
addressDictionary:(NSDictionary<NSString *,id> *)addressDictionary;]
Related
I have created a map that has an MKPointAnnotation which is the single point on the map (besides the users location). I am trying to work out how to amend some existing code I have to get the Driving directions to this point.
This is the code that I use early in the application. At this earlier point in the application I have the following which gives me a CLPlaceMark.
[geocoder geocodeAddressString:location
completionHandler:^(NSArray* placemarks, NSError* error){
if (placemarks && placemarks.count > 0) {
CLPlacemark *topResult = [placemarks objectAtIndex:0];
Collecting directions:
MKDirectionsRequest *request = [[MKDirectionsRequest alloc] init];
[request setSource:[MKMapItem mapItemForCurrentLocation]];
MKPlacemark *mkDest = [[MKPlacemark alloc] initWithPlacemark:topResult];
[request setDestination:[[MKMapItem alloc] initWithPlacemark:mkDest]];
[request setTransportType:MKDirectionsTransportTypeWalking]; // This can be limited to automobile and walking directions.
[request setRequestsAlternateRoutes:NO];
MKDirections *directions = [[MKDirections alloc] initWithRequest:request];
[directions calculateDirectionsWithCompletionHandler:^(MKDirectionsResponse *response, NSError *error) {
if (!error) {
for (MKRoute *route in [response routes]) {
[self.mapView addOverlay:[route polyline] level:MKOverlayLevelAboveRoads]; // Draws the route above roads, but below labels.
// You can also get turn-by-turn steps, distance, advisory notices, ETA, etc by accessing various route properties.
}
}
}];
Issue
The issue is that later on I seem to only be able to access the self.mapView.annotations. So I have access to the MKPointAnnotation, but I need access to a CLPlacemark for the setDestination on the MKDirectionsRequest.
So the question is how do I get a CLPacemark from a MKPointAnnotation, or is there a different approach to getting directions to a single point without that requirement? Thanks
For the directions request, the MKMapItem needs an MKPlacemark (not a CLPlacemark).
You can create an MKPlacemark directly from coordinates using its initWithCoordinate:addressDictionary: method.
For example:
MKPlacemark *mkDest = [[MKPlacemark alloc]
initWithCoordinate:pointAnnotation.coordinate
addressDictionary:nil];
[request setDestination:[[MKMapItem alloc] initWithPlacemark:mkDest]];
MKPointAnnotation will give you the coordinate, which you can put into CLPlacemark's location.coordinate. I don't think there will be any other readily-available information within an MKPointAnnotation that would be usable in a CLPlacemark.
MKPointAnnotation *annotation = ...;
CLPlacemark *placemark = ...;
placemark.location.coordinate = annotation.coordinate;
Edit: Apologies, I didn't realize that CLPlacemarks are largely read-only. That being said, you can use a reverse-geocode on the coordinate of your MKPointAnnotation in order to get a CLPlacemark. This link has information on how to reverse-geocode to get your CLPlacemark from a CLLocation (populate the location.coordinate with your annotation.coordinate) to look up directions.
You need to use the coordinate property of the MKPointAnnotation and then get the CLPlacemark via CLGeocoder using reverse geocode.
EDIT: Some sample code.
CLLocation *location = [[CLLocation alloc] initWithLatitude:annotation.coordinate.latitude longitude:annotation.coordinate.longitude];
CLGeocoder *geocoder = [CLGeocoder new];
[geocoder reverseGeocodeLocation:location completionHandler:^(NSArray *placemark, NSError *error){
// Grab the placemark
}];
Otherwise you need to cache the CLPlacemark which you can do on your annotation datasource if you like (remember MKAnnotation is a protocol, there is nothing saying you can't add a property to the backing model).
Hi I have a file on a server json format with several map points:
["VALUE","proyect",[{"id":"1","name":"Frankie Johnnie & Luigo Too","address":"939 W El Camino Real, Mountain View, CA","lat":"37.386337","lng":"-122.085823"},morepoints..]]
I want to put these points as markers on my map in my ios app.
I've looked at several pages and here but I have not found anything and what I have tried does not work.
Any help,please?
First, you first parse your JSON:
NSError *error;
NSArray *array = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
NSAssert(array, #"%s: JSONObjectWithData error: %#", __FUNCTION__, error);
Second, you can then grab the array of locations out of that top-level array, create annotations for each location dictionary in that array, and add it to your map:
// the array of locations is the third object in the top-level array
NSArray *locations = array[2];
// now iterate through this array of locations
for (NSDictionary *location in locations)
{
// grab the latitude and longitude strings
NSString *latitudeString = location[#"lat"];
NSAssert(latitudeString, #"No latitude");
NSString *longitudeString = location[#"lng"];
NSAssert(longitudeString, #"No longitude");
// create the annotation and add it to the map
MKPointAnnotation *annotation = [[MKPointAnnotation alloc] init];
annotation.coordinate = CLLocationCoordinate2DMake([latitudeString doubleValue], [longitudeString doubleValue]);
annotation.title = location[#"name"];
annotation.subtitle = location[#"address"];
[self.mapView addAnnotation:annotation];
}
Having said that, I must say that I don't care for the JSON format:
["VALUE","proyect",[{"id":"1","name":"Frankie Johnnie & Luigo Too","address":"939 W El Camino Real, Mountain View, CA","lat":"37.386337","lng":"-122.085823"},morepoints..]]
It's a questionable design for us to have to divine what the first, second, and third objects in this array are. We shouldn't have to cryptically grab array[2] to get the array of locations.
Arrays should be used for lists of equivalent items (e.g. the array of locations makes perfect sense). But this top level array is a little more dubious, with three very semantically different types of values, with nothing in the JSON to indicate what these three items are.
If you're stuck with this JSON format, then, fine, the above code should do the job. But hopefully you can change the top level structure here to be a dictionary, rather than an array, where you can use key names to identify the various items in this top-level dictionary, e.g.:
{"id" : "VALUE", "project" : "proyect", "locations" : [{"id":"1","name":"Frankie Johnnie & Luigo Too","address":"939 W El Camino Real, Mountain View, CA","lat":"37.386337","lng":"-122.085823"},morepoints..]}
I didn't know what those first two values were supposed to be, so I just called them id and project, but you should obviously use names that truly reflect what those first two values are. But the key concept is to use a dictionary, where you can refer to the array of locations by name. For example, if the JSON was changed like I've suggested here, the code to parse it would become:
NSError *error;
NSDictionary *dictionary = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
NSAssert(dictionary, #"%s: JSONObjectWithData error: %#", __FUNCTION__, error);
NSArray *locations = dictionary[#"locations"];
Also, as a stylistic observation, many people would generally represent the latitude and longitude values as numbers (without the quotation marks), and the NSJSONSerialization would parse them as NSNumber objects rather than NSString objects. But that's up to you.
You'll want to parse the JSON to retrieve the data for each point and then create an MKMapViewAnnotation instance of your own for each and add them to the map.
More info here: http://maybelost.com/2011/01/a-basic-mapview-and-annotation-tutorial/
and here: How to add a Map annotation on MKMapView?
Info about parsing JSON here: http://www.appcoda.com/fetch-parse-json-ios-programming-tutorial/
I have a method that is triggered on the press of a button. This is most of the implmentation:
[self.placeDictionary setValue:#"166 Bovet Rd" forKey:#"Street"];
[self.placeDictionary setValue:#"San Mateo" forKey:#"City"];
[self.placeDictionary setValue:#"CA" forKey:#"State"];
[self.placeDictionary setValue:#"94402" forKey:#"ZIP"];
CLGeocoder *geocoder = [[CLGeocoder alloc] init];
[geocoder geocodeAddressDictionary:self.placeDictionary completionHandler:^(NSArray *placemarks, NSError *error) {
if([placemarks count]) {
CLPlacemark *placemark = [placemarks objectAtIndex:0];
CLLocation *location = placemark.location;
CLLocationCoordinate2D coordinate = location.coordinate;
PFGeoPoint* userLocation = [PFGeoPoint geoPointWithLatitude:coordinate.latitude longitude:coordinate.longitude];
NSLog(#"%f,%f", userLocation.latitude, userLocation.longitude);
} else {
NSLog(#"location error");
return;
}
}];
However, I am getting the following exception:
*** WebKit discarded an uncaught exception in the webView:shouldInsertText:replacingDOMRange:givenAction: delegate: <NSUnknownKeyException> [<__NSDictionaryI 0x873a3c0> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key Street.
I have absolutely no idea what this exception means. Could someone help me understand why it is producing this?
First off, you are trying to add objects to an immutable dictionary. The part of the exception beginning [<__NSDictionaryI 0x873a3c0> setValue:forUndefinedKey: gives a class name of __NSDictionaryI, which is an immutable member of the NSDictionary class cluster - so you cannot add any objects to it at run-time. You need to ensure that self.placeDictionary is assigned to an NSMutableDictionary instance before this code is called.
Unfortunately, you are also using the wrong method to add the objects - you're using setValue:forKey: instead of setObject:forKey:. Since this method is part of the NSKeyValueCoding informal protocol, you're not stopped from doing this at compile time. You should instead use setObject:forKey: which is the correct method to set key-value pairs on an NSMutableDictionary. After you correct the first issue, replace the setValue:forKey: calls with setObject:forKey:, for example:
[self.placeDictionary setObject:#"San Mateo" forKey:#"City"];
I have an app that fetches all data from the db and plots it on a map. Currently it plots everything in my city. So upon opening, it centers around my device location and plots all locations in the CoreData. But the COreData has locations from various cities.
So currently the app centers around Miami and i can see the Miami locations. If I were to drive to New York, it would center around me of course and plot those locations but it would also plot the Miami ones even though I would not see them on the map.
But if I want the user to be able to select a city he/she IS NOT currently in, and have the map center on THAT point and plot its locations, how do I get from the user selected city (a string) to the actual coordinate?
You can use geocoder
Make an addressDictionary based on these keys -
City, Street, Country , ZIP, State - If you only want city make a dictionary like this
NSDictionary *addressDictionary = [[NSDictionary alloc]initWithObjectsAndKeys:#"Miami",#"City", nil];
add CoreLocation.framework and import #import
use the following code to find the location -
CLGeocoder *geocoder = [[CLGeocoder alloc] init];
[geocoder geocodeAddressDictionary:addressDictionary completionHandler:^(NSArray *placemarks, NSError *error) {
if([placemarks count]) {
CLPlacemark *placemark = [placemarks objectAtIndex:0];
CLLocation *location = placemark.location;
CLLocationCoordinate2D coordinate = location.coordinate;
[self.googleMap animateToLocation:coordinate];
I have an array of locations and when I add another I want to be able to check if the other locations in the array are within a block of the new one. This is the Code I have to find the current location:
//Geocoding Block
[self.geoCoder reverseGeocodeLocation: locationManager.location completionHandler:
^(NSArray *placemarks, NSError *error) {
//Get nearby address
CLPlacemark *placemark = [placemarks objectAtIndex:0];
//String to hold address
locatedAt = [[placemark.addressDictionary valueForKey:#"FormattedAddressLines"] componentsJoinedByString:#", "];
The array has yet to be created because I want to figure this out first, I dont know what should be held in the array (string...). I know how to do a search I just need to know how to compare the locations.
You can get the distance between two locations using the distanceFromLocation: method on CLLocation. (You can get a CLLocation out of a CLPlacemark with myPlacemark.location.) So if you have an array of CLLocation objects, and you want to find the ones that are within one block (1/20 mile, or about 80 meters), you can do this:
NSMutableArray *locationsWithinOneBlock = [NSMutableArray new];
for (CLLocation *location in myLocations) {
if ([location distanceFromLocation:targetLocation] <= 80.0)
[locationsWithinOneBlock addObject:location];
}
This assumes you have an array myLocations of CLLocation objects that you want to filter against a single CLLocation called targetLocation.