I uses CLGeocoder to decode the CLLocation from longitude/latitude to place names. It works fine. But there is still one thing bothers me. When i set the device language to English, the result of the code below:
- (void)locationManager:(CLLocationManager *)manager
didUpdateToLocation:(CLLocation *)newLocation
fromLocation:(CLLocation *)oldLocation{
/* We received the new location */
NSLog(#"Latitude = %f", newLocation.coordinate.latitude);
NSLog(#"Longitude = %f", newLocation.coordinate.longitude);
[self.geoCoder reverseGeocodeLocation:newLocation completionHandler:^(NSArray* placemarks, NSError* error){
MKPlacemark *placemarker = [placemarks objectAtIndex:0];
NSLog(#"%#",placemarker.locality);
}];
[self.locationManager stopUpdatingLocation];
}
is displayed in english, like : chengdu.
when i change the device language to Chinese,
placemarker.locality
returns a Chinese character value.
But what i really want is that it will always return an English character value (no Chinese character value).
I guess this is something related to the locale. Can anyone help on this? Thanks.
Usually, it is not a good practice to mess with user locales. If the device language is set to Chinese is because the user want to read Chinese characters so, why do you want to show him in English when he already told you that he want Chinese?
Anyway, if for any reason you need to force english, you can trick the geoCoder which uses the standardUserDefaults first language so you can do something like this just before calling the geoCoder method:
[[NSUserDefaults standardUserDefaults] setObject:[NSArray arrayWithObjects:#"en", nil] forKey:#"AppleLanguages"];
This way, geoCoder will give you all the information in english.
However, this will change the user preferences so it is a best approach to give them back to where they were:
NSMutableArray *userDefaultLanguages = [[NSUserDefaults standardUserDefaults] objectForKey:#"AppleLanguages"];
[[NSUserDefaults standardUserDefaults] setObject:[NSArray arrayWithObjects:#"en", nil] forKey:#"AppleLanguages"];
[self.geoCoder reverseGeocodeLocation:newLocation completionHandler:^(NSArray* placemarks, NSError* error){
MKPlacemark *placemarker = [placemarks objectAtIndex:0];
NSLog(#"%#",placemarker.locality);
}];
[[NSUserDefaults standardUserDefaults] setObject:userDefaultLanguages forKey:#"AppleLanguages"];
As I said, you should really think why you need this, but if you really need this, that would work.
I found a nice solution
NSString *country = placemark.ISOcountryCode;
This will return the exact country no matter your locale is. For example country will be #"US" instead of #"United States"
From ios 11 you can pass a preferredLocale parameter to geocoder reverseGeocodeLocation method.
In Swift:
geocoder.reverseGeocodeLocation(
location: CLLocation,
preferredLocale: Locale?,
completionHandler: {}
)
A preferredLocale value example:
Locale(identifier: "en_US")
Related
I am trying to get user's region code in swift 3 by using:
Locale.current.regionCode
Unfortunately regionCode is nil, do you have an idea why?
And what should I do to obtain it?
Thanks a lot.
For those looking for solutions at SIMULATOR, go to "Settings > General > Language & Region" and change the region.
It worked for me and other people.
For a weird unknown reason, some simulators doesn't return the region until it changes at least once.
I don't know if it works at real device as well, because I did not had this problem on real device.
I ran into the same issue and as it turns out it was because I set my Scheme to a specific language. If you set both, Application Language & Application Region to "System Language / Region" it should work.
Many of the properties on Locale can return nil for various reasons on iOS (and Android).
You may also use objectForKey:NSLocaleCountryCode to query the country code.
// code may be nil
NSString *code = [[NSLocale currentLocale] objectForKey:NSLocaleCountryCode];
It's a good idea to have a fallback logic to find countryCode with CoreTelephony.
CTCarrier *carrier = [[CTTelephonyNetworkInfo new] subscriberCellularProvider];
NSString *countryCode = carrier.isoCountryCode;
Or/And with reverse geocode:
// CLLocationManagerDelegate
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray<CLLocation *> *)locations {
__block CLLocation *location = [locations firstObject];
[[[CLGeocoder alloc] init] reverseGeocodeLocation:location completionHandler:^(NSArray<CLPlacemark *> * _Nullable placemarks, NSError * _Nullable error) {
if (!error && placemarks.count > 0) {
CLPlacemark *placemark = [placemarks firstObject];
// Use placemark.country;
// Use placemark.ISOCountryCode;
}
}
}
For instance, on Android version of corresponding region query for Locale, it's nil for many of the rooted devices.
The documentation for regionCode states
The region code of the locale, or nil if it has none.
Some locales simply do not have a region (aka country) code. However I don't know which ones do not.
I am using apple map, My app has multiple languages and I want to change the name of the location in the language selected within the app. I am getting detailed information of locality using CLPlacemark via reversegeocoding.
Here is my code
CLPlacemark *placemarker = [placemarks lastObject];
NSString *locality = placemarker.thoroughfare ?: placemarker.subLocality ?: placemarker.locality;
but I am unable to get place name in selected language of the app.
Got solution to my own problem working fine, Here is my code
[[NSUserDefaults standardUserDefaults] setObject:[NSArray arrayWithObjects:[[REAAppSettingsController sharedInstance] languageTag], nil] forKey:#"AppleLanguages"];
[self.geocoding reverseGeocodeLocation:testLocation completionHandler:^(NSArray *placemarks, NSError *error) {
CLPlacemark *placemarker = [placemarks lastObject];
NSString *locality = placemarker.thoroughfare ?: placemarker.subLocality ?: placemarker.locality;
if (locality)
{
completion(locality);
}
}
}];
I added a manual language section inside my iOS App where you can change the language the app should be displayed in. If someone chooses to select a manual language I override the "AppleLanguages" standardUserDefaults like so
NSString *language = [[[NSUserDefaults alloc] initWithSuiteName:kAppGroup] objectForKey:kManualLanguageKey];
[[NSUserDefaults standardUserDefaults] setObject:[NSArray arrayWithObjects:language, nil]
forKey:#"AppleLanguages"];
Now when the user restarts the app (after terminating it) the App automatically loads the correct LocalizedStrings.
Now here is my problem:
even though this solves my problem of changing the language of the app it does not display numbers etc. correctly. Number/DateFormatter and localizedStringWithFormat depend on the [NSLocale currentLocale]. I know I could just overrite the current lokale as well like so
[[NSUserDefaults standardUserDefaults] setObject:language
forKey:#"AppleLocale"];
but then I can't get the real language/region selected in the system settings once the user decides to disable the manual language. I could store the currentLocale inside my own userDefaults before I override it but then if the user decides (for whatever reason) to change the system language while the manual language in the app is active I won't be able to get this new selected system language.
Is there any way to get the right format of Numbers and Dates without manually changing the locale property of NumberFormatter etc.?
Ok, I found a way to reset the current locale after the user disables the manual language on http://www.thetawelle.de/?p=3800
In main.m if the bool for manual language is false, I reset the current language like this:
NSArray *keysToRemove = #[#"AppleLanguages",#"NSLanguages",#"AppleLocale"];
NSLog( #"RESETTING TO USE SYSTEM LOCALE" );
#try {
for( NSString *currentKey in keysToRemove ) {
if( [defaults objectForKey:currentKey] ) {
[defaults removeObjectForKey:currentKey];
}
}
}
#catch (NSException *exception) {
// NOTHNG TO CATCH HERE
}
#finally {
[defaults synchronize];
}
after that, the current locale and language are back to the language and region selected in the system settings.
I'm attempting to build an application that builds and saves routes similar to map my run. I'm using the Breadcrumb sample code, specifically the CrumbPath and CrumbPathView as the base of my routes, from Apple. Two questions:
If I try to access the MKMapPoint *points object of the CrumbPath like so:
[_route lockForReading];
NSLog(#"%#", _route.points);
NSLog(#"%d", _route.pointCount);
[_route unlockForReading];
my app crashes, saying:
Thread 1: EXC_BAD_ACCESS (code: 1, address: 0x9450342d)
Which I have a hard time understanding, because within the CrumbPath.m file, the folks at apple write to the "array" by explicitly acquiring the write lock, and then unlocking it, but if I acquire the read lock and attempt to read from it, it crashes.
The reason I attempt to access the points is in an attempt to get the MKMapPoints, convert them to CLLocationCoordinate2D objects, and save them so I can redraw the polyline at the user's request. Since I cannot get access to the points, I attempt to save the CLLocationCoordinate2D objects from my locationManager that I send to the _route in an array to upload to my Parse backend, but I always get an error saying:
Sending 'CLLocationCoordinate2D' to parameter of incompatible type 'id'
Which isn't making this any easier. Does anybody have any insight to why I'm getting these errors?
Location Manager Delegate
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations {
if (_userLocation.longitude != manager.location.coordinate.longitude
&& _userLocation.latitude != manager.location.coordinate.latitude) {
_userLocation = manager.location.coordinate;
}
if (_isRecording) {
if (!_route) {
NSLog(#"lat: %f, lng: %f", _userLocation.latitude, _userLocation.longitude);
_route = [[CrumbPath alloc] initWithCenterCoordinate:_userLocation];
[_mapView addOverlay:_route];
MKCoordinateRegion region = MKCoordinateRegionMakeWithDistance(_userLocation, 2000, 2000);
[_mapView setRegion:region animated:YES];
}else {
MKMapRect updateRect = [_route addCoordinate:_userLocation];
if (!MKMapRectIsNull(updateRect)) {
MKZoomScale currentZoomScale = (CGFloat)(_mapView.bounds.size.width / _mapView.visibleMapRect.size.width);
CGFloat lineWidth = MKRoadWidthAtZoomScale(currentZoomScale);
updateRect = MKMapRectInset(updateRect, -lineWidth, -lineWidth);
[_routeView setNeedsDisplayInMapRect:updateRect];
}
}
[_routePoints addObject:_userLocation];
[_route lockForReading];
NSLog(#"%d", _route.pointCount);
NSLog(#"%#", _route.points);
[_route unlockForReading];
}
}
Stop Recording Logic
//stop recording
NSLog(#"STOP");
if (_route) {
NSLog(#"There is a route");
//Show route options toolbar
[_route lockForReading];
NSLog(#"%#", _route);
NSLog(#"%d", _route.pointCount);
NSLog(#"%#", _route.points);
PFObject *routeToSave = [PFObject objectWithClassName:#"Routes"];
//[routeToSave setObject:_route forKey:#"routePoints"];
[_route unlockForReading];
[routeToSave saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if (!error) {
NSLog(#"%c", succeeded);
}else {
NSLog(#"%#", error);
}
}];
}
Regarding your first issue, the crash was because of this:
NSLog(#"%#", _route.pointCount);
It should be:
NSLog(#"%d", _route.pointCount);
As mentioned in my comments, %d should be used for count and %# will cause a crash.
Regarding your second issue, you cannot add a c struct to an NSArray. You should wrap it in NSValue before adding it to an array. CLLocationCoordinate2D is a c-struct. Check the documentation here.
Change this:
[_routePoints addObject:_userLocation];
to:
NSValue *aValue = [NSValue valueWithMKCoordinate:_userLocation];
[_routePoints addObject:aValue];
To get the coordinate back from NSValue, you can use,
[aValue MKCoordinateValue];
As mentioned in your error message, you were trying to add CLLocationCoordinate2D to an array which expects an object.
Whatever api you're using to talk to parse is expecting an id which is a pointer to any object. A cllocationcoordinate2d is a c-struct of two doubles and not an object if I'm not mistaken. You should probably create a little wrapper object to save those two doubles and convert them to/from CLLocationCoordinate2d items.
1:
Line: NSLog(#"%#", _route.points); is wrong
_route.points is not a String, and you are using the NSStrig formating symbol "%#".
Further:
Since CLLocationCoordinate2D is a C-Sruct and not an Objective-C Object, you probaly want to create an own GeoPoint class.
I have a simple locations map and I want to make my app beep when the user is approaching a location that is listed in a remote file
the listed locations are on my server named locations.txt
how can i check locations.txt every 1 minute to see if the user is within 300m of a location??
The standard answer to this question is Shape-Based Regions as described in the Location Awareness Guide. Generally, shape-based regions is the way to go if you have a limited number of regions. But, given that you want a lot of regions, you might have to "roll your own":
Turn on a location service and monitor your location. See the Location Awareness Programming Guide. If you use standard location service, make sure to set a desiredAccuracy that is as low as possible to achieve the functional need (e.g. kCLLocationAccuracyHundredMeters).
Once you've successfully received the first didUpdateLocations, if you really want to check every minute, you could create a timer at that point. If the purpose of that timer is to just check the users' location, then the timer is really not needed and you can just wait for occurrences of the didUpdateLocations.
You can iterate through your array of locations to monitor (I'd convert them to CLLocation objects) and simply use distanceFromLocation.
A couple of observations, though:
You suggest that you want to check locations.txt every minute to see if user is within 300m of location. I can imagine two reasons why you might have proposed that solution:
Did server's locations.txt change? If this is the problem you're trying to solve, a better solution would be push notifications (a.k.a. "remote notifications") and you want to make sure the client has access to the latest information. The process of constantly re-retrieving the file is very expensive (in terms of bandwidth, battery, computationally); or
Did the user move? If you're concerned about whether the user may have moved, the right solution is not to check every minute, but rather wait for the [CLLocationManagerDelegate] instance method didUpdateLocations to be called. If you want to avoid too many redundant checks, you can always keep track of whether the last request took place less than a minute ago or not, and then only check again if it was more than a minute ago. But that's very different than checking every minute whether you need to or not.
You've suggested using a text file. You might want to contemplate using a JSON file (or XML file), which is a better mechanism for retrieving data from a server.
For example, if you have a text file in JSON format, you can parse the results in another single line of code (JSONObjectWithData). To illustrate, let me show you what a JSON file might look like (where the square brackets designate an array, and the curly braces designate a dictionary, this is therefore an array of dictionaries):
[
{
"name" : "Battery Park",
"latitude" : 40.702,
"longitude" : -74.015
},
{
"name" : "Grand Central Station",
"latitude" : 40.753,
"longitude" : -73.977
}
]
Then your app can retrieve the results incredibly easily with two lines:
NSData *locationsData = [NSData dataWithContentsOfURL:url];
NSArray *locationsArray = [NSJSONSerialization JSONObjectWithData:locationsData options:0 error:&error];
So, you'll need to start location services:
if (nil == self.locationManager)
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.delegate = self;
self.locationManager.desiredAccuracy = kCLLocationAccuracyKilometer;
// Set a movement threshold for new events.
self.locationManager.distanceFilter = 500;
[self.locationManager startUpdatingLocation];
You'll then have a routine for checking the current location:
- (void)checkLocation
{
NSURL *url = [NSURL URLWithString:kLocationsUrlString];
NSData *locationsData = [NSData dataWithContentsOfURL:url];
NSAssert(locationsData, #"failure to download data"); // replace this with graceful error handling
NSError *error;
NSArray *locationsArray = [NSJSONSerialization JSONObjectWithData:locationsData
options:0
error:&error];
NSAssert(locationsArray, #"failure to parse JSON"); // replace with with graceful error handling
for (NSDictionary *locationEntry in locationsArray)
{
NSNumber *longitude = locationEntry[#"longitude"];
NSNumber *latitude = locationEntry[#"latitude"];
NSString *locationName = locationEntry[#"name"];
CLLocation *location = [[CLLocation alloc] initWithLatitude:[latitude doubleValue]
longitude:[longitude doubleValue]];
NSAssert(location, #"failure to create location");
CLLocationDistance distance = [location distanceFromLocation:self.locationManager.location];
if (distance <= 300)
{
NSLog(#"You are within 300 meters (actually %.0f meters) of %#", distance, locationName);
}
else
{
NSLog(#"You are not within 300 meters (actually %.0f meters) of %#", distance, locationName);
}
}
}
And this will be called when the user's location changes:
// this is used in iOS 6 and later
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
[self checkLocation];
}
// this is used in iOS 5 and earlier
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation
{
if ([[[UIDevice currentDevice] systemVersion] floatValue] < 6.0)
[self checkLocation];
}
The implementation might look like this test project on GitHub. This is a barebones implementation, but it gives you an idea of the tools you have at hand, namely retrieving your locations.json file and comparing that to the location retrieved by the device.