Reputation: 6666
I need to get the country location of a iOS device.
I've been trying to use CoreLocation with MKReverseGeocoder. However this seems to return erraneous quite frequently. And I only need the country, no need for streets and such.
How can this be done in a more stable way?
Upvotes: 81
Views: 83559
Reputation: 119917
/// 1️⃣ Get the current region center from a **non-zero-frame** map view
let regionCenter = MKMapView(frame: CGRect(origin: .zero, size: CGSize(width: 1, height: 1))).region.center
/// 2️⃣ Convert the location type
let location = CLLocation(latitude: regionCenter.latitude, longitude: regionCenter.longitude)
/// 3️⃣ Reverse geo code the location:
let placemark = try? await CLGeocoder().reverseGeocodeLocation(location).first
print(placemark?.country)
You can also use the callback version of step 3, if you don't like the await part.
Upvotes: 0
Reputation: 1049
I managed to get the country without asking for location permissions using the following approach:
import MapKit
class CountryDectectorViewController: UIViewController {
var didDetectCountryCode: ((String?) -> Void)?
override func viewDidLoad() {
super.viewDidLoad()
// Map view setup
let mapView = MKMapView()
view.addSubview(mapView)
mapView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
mapView.topAnchor.constraint(equalTo: view.topAnchor),
mapView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
mapView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
mapView.trailingAnchor.constraint(equalTo: view.trailingAnchor)
])
mapView.layoutIfNeeded()
// Reverse geocoding map region center
let location = CLLocation(
latitude: mapView.region.center.latitude,
longitude: mapView.region.center.longitude
)
CLGeocoder().reverseGeocodeLocation(location) { placemarks, _ in
self.didDetectCountryCode?(placemarks?.first?.isoCountryCode)
}
}
}
To ISO Country Code can be then obtained using:
let controller = CountryDectectorViewController()
controller.loadViewIfNeeded()
controller.didDetectCountryCode = { countryCode in
print(countryCode)
}
Some context
I realised that UIKit has this information already because everytime the MKMapView
is shown, the region is automatically set to fit the current user's country. Using this hypothesis I needed to find a solution to load the map without presenting it and then to reverse geocode the center coordinates to identify the country.
I implemented this solution taking into consideration the following limitations I found:
Upvotes: 8
Reputation: 5853
Use the CoreTelephony
method as a fallback if Locale.current.regionCode
doesn't work for some reasons.
import CoreTelephony
if let carriers = CTTelephonyNetworkInfo().serviceSubscriberCellularProviders?.values, let countryCode = Array(carriers).compactMap { $0.isoCountryCode }.first {
print("❤️ \(countryCode)")
}
Upvotes: 6
Reputation: 5241
if let countryCode = Locale.current.regionCode {
let country = Locale.current.localizedString(forRegionCode: countryCode)
}
Upvotes: -1
Reputation: 593
As mentioned by @Denis Locale
is just a setting about currently used regional settings, it doesn't mean the actual country you're in.
However, suggested use of CLLocationManager
to get current location & CLGeocoder
to perform reverse-geocoding, means prompting user access to Location Services.
How about getting country code from mobile carrier?
import CoreTelephony
guard carrier = CTTelephonyNetworkInfo().subscriberCellularProvider else {
//iPad
return
}
let countryST = carrier.isoCountryCode!
Upvotes: 12
Reputation: 1325
Swift 4.0 code for getting the Country name as per region set:
let countryLocale = NSLocale.current
let countryCode = countryLocale.regionCode
let country = (countryLocale as NSLocale).displayName(forKey: NSLocale.Key.countryCode, value: countryCode)
print(countryCode, country)
prints: Optional("NG") Optional("Nigeria"). //for nigeria region set
Upvotes: 5
Reputation: 341
@Rob
let locale = Locale.current
let code = (locale as NSLocale).object(forKey: NSLocale.Key.countryCode) as! String?
using these code you will get your region country code and if you didn't get still then change it just go in Phone setting->general->language & region and set your region you want
Upvotes: -1
Reputation: 361
Here is @Denis's and @Matt's answers put together for a Swift 3 solution:
import UIKit
import CoreLocation
class ViewController: UIViewController, CLLocationManagerDelegate {
let locationManager = CLLocationManager()
let geoCoder = CLGeocoder()
override func viewDidLoad() {
super.viewDidLoad()
locationManager.requestAlwaysAuthorization()
if CLLocationManager.locationServicesEnabled() {
locationManager.delegate = self
locationManager.startMonitoringSignificantLocationChanges()
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let currentLocation = locations.first else { return }
geoCoder.reverseGeocodeLocation(currentLocation) { (placemarks, error) in
guard let currentLocPlacemark = placemarks?.first else { return }
print(currentLocPlacemark.country ?? "No country found")
print(currentLocPlacemark.isoCountryCode ?? "No country code found")
}
}
}
Don't forget to set the NSLocationAlwaysUsageDescription
or NSLocationWhenInUseUsageDescription
in Info.plist
as well!
Upvotes: 13
Reputation: 717
Here's a quick loop in Swift 3 that returns a complete list of country codes.
let countryCode = NSLocale.isoCountryCodes
for country in countryCode {
print(country)
}
Upvotes: -1
Reputation: 3015
NSString *countryCode = [[NSLocale currentLocale] objectForKey: NSLocaleCountryCode];
will get you an identifier like e.g. "US" (United States), "ES" (Spain), etc.
In Swift 3:
let countryCode = NSLocale.current.regionCode
In Swift 2.2:
let countryCode = NSLocale.currentLocale().objectForKey(NSLocaleCountryCode) as String
Compared to a solution based on CLLocationManager this approach has pros and cons. The primary con is that it doesn't guarantee that this is where the device is physically if the user configures it differently. This can however also be seen as a pro since it instead shows which country a user is mentally/culturally aligned with - so if e.g. I go abroad on vacation then the locale is still set to my home country. However a pretty big pro is that this API doesn't require user permission like CLLocationManager does. So if you haven't already gotten permission to use the user's location, and you can't really justify throwing a popup dialog in the user's face (or they already rejected that popup and you need a fallback) then this is probably the API you want to use. Some typical use cases for this could be personalization (e.g. culturally relevant content, default formats, etc.) and analytics.
Upvotes: 121
Reputation: 5792
For Swift 3 it's even simpler:
let countryCode = Locale.current.regionCode
Upvotes: 4
Reputation: 3726
NSLocale *countryLocale = [NSLocale currentLocale];
NSString *countryCode = [countryLocale objectForKey:NSLocaleCountryCode];
NSString *country = [countryLocale displayNameForKey:NSLocaleCountryCode value:countryCode];
NSLog(@"Country Locale:%@ Code:%@ Name:%@", countryLocale, countryCode, country);
//Country Locale:<__NSCFLocale: 0x7fd4b343ed40> Code:US Name:United States
Upvotes: 5
Reputation: 1131
@Denis's answer is good -- here is some code putting his answer into practice. This is for a custom class that you have set to conform to the CLLocationManagerDelegate
protocol. It's a little simplified (e.g. if the location manager returns multiple locations, it just goes with the first one) but should give folks a decent start...
- (id) init //designated initializer
{
if (self)
{
self.locationManager = [[CLLocationManager alloc] init];
self.geocoder = [[CLGeocoder alloc] init];
self.locationManager.delegate = self;
[self.locationManager startMonitoringSignificantLocationChanges];
}
return self;
}
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
if (locations == nil)
return;
self.currentLocation = [locations objectAtIndex:0];
[self.geocoder reverseGeocodeLocation:self.currentLocation completionHandler:^(NSArray *placemarks, NSError *error)
{
if (placemarks == nil)
return;
self.currentLocPlacemark = [placemarks objectAtIndex:0];
NSLog(@"Current country: %@", [self.currentLocPlacemark country]);
NSLog(@"Current country code: %@", [self.currentLocPlacemark ISOcountryCode]);
}];
}
Upvotes: 15
Reputation: 1207
You can get NSTimeZone from CLLocation: https://github.com/Alterplay/APTimeZones and works locally.
Upvotes: 1
Reputation: 263
Here's an alternative, perhaps overly circuitous method. The other solutions are based on manual settings (NSLocale) or on requesting for permission to use location services which can be denied (CLLocationManager), so they have drawbacks.
You can get the current country based on the local timezone. My app is interfacing with a server running Python with pytz installed, and that module provides a dictionary of country codes to timezone strings. I only really need to have the server know the country so I don't have to set it up entirely on iOS. On the Python side:
>>> import pytz
>>> for country, timezones in pytz.country_timezones.items():
... print country, timezones
...
BD ['Asia/Dhaka']
BE ['Europe/Brussels']
BF ['Africa/Ouagadougou']
BG ['Europe/Sofia']
BA ['Europe/Sarajevo']
BB ['America/Barbados']
WF ['Pacific/Wallis']
...
On the iOS side:
NSTimeZone *tz = [NSTimeZone localTimeZone];
DLog(@"Local timezone: %@", tz.name); // prints "America/Los_Angeles"
I have my server send in the local timezone name and look it up in the pytz country_timezones dictionary.
If you make an iOS version of the dictionary available in pytz or some other source, you can use it to immediately look up the country code without the help of a server, based on timezone settings, which are often up to date.
I may be misunderstanding NSLocale though. Does it give you the country code through regional formatting preferences or timezone settings? If the latter, then this is just a more complicated way of getting the same result...
Upvotes: 10
Reputation: 17317
If you are only interested in telephone devices, then the technique mentioned here might be useful to you: Determine iPhone user's country
Upvotes: 0
Reputation: 6413
NSLocale
is just a setting about currently used regional settings, it doesn't mean the actual country you're in.
Use CLLocationManager
to get current location & CLGeocoder
to perform reverse-geocoding. You can get country name from there.
Upvotes: 52