How would you recommend to get the user country without the user's permission? Is it possible to use cell tower triangulation or WiFi positioning to get an approximate location? What other way do you suggest to guess what country the user is located?
-
I've updated my answer, this might help you. – えるまる Apr 09 '20 at 08:46
2 Answers
From apple's documentation
Important: In addition to hardware not being available, the user has the option of denying an application’s access to location service data. During its initial uses by an application, the Core Location framework prompts the user to confirm that using the location service is acceptable. If the user denies the request, the CLLocationManager object reports an appropriate error to its delegate during future requests. You can also check the application’s explicit authorization status using the authorizationStatus method.
So even if there exist some tricky way to avoid permission, you will be rejected at review stage.
What other way do you suggest to guess what country the user is located?
You can get this information by using ip address.
Swift 4 - Get device's IP Address:
Add #include<ifaddrs.h>
in your bridging header.
This is the framework needed to get IP address.
class func getIPAddress() -> String? {
var address: String?
var ifaddr: UnsafeMutablePointer<ifaddrs>? = nil
if getifaddrs(&ifaddr) == 0 {
var ptr = ifaddr
while ptr != nil {
defer { ptr = ptr?.pointee.ifa_next }
let interface = ptr?.pointee
let addrFamily = interface?.ifa_addr.pointee.sa_family
if addrFamily == UInt8(AF_INET) || addrFamily == UInt8(AF_INET6) {
if let name: String = String(cString: (interface?.ifa_name)!), name == "en0" {
var hostname = [CChar](repeating: 0, count: Int(NI_MAXHOST))
getnameinfo(interface?.ifa_addr, socklen_t((interface?.ifa_addr.pointee.sa_len)!), &hostname, socklen_t(hostname.count), nil, socklen_t(0), NI_NUMERICHOST)
address = String(cString: hostname)
}
}
}
freeifaddrs(ifaddr)
}
return address
}
You could start by trying http://ip-api.com/json
, which returns a JSON as explained on their API page.
You can then convert this JSON string to a dictionary and access the data.
func getIpLocation(completion: @escaping(NSDictionary?, Error?) -> Void)
{
let url = URL(string: "http://ip-api.com/json")!
var request = URLRequest(url: url)
request.httpMethod = "GET"
URLSession.shared.dataTask(with: request as URLRequest, completionHandler:
{ (data, response, error) in
DispatchQueue.main.async
{
if let content = data
{
do
{
if let object = try JSONSerialization.jsonObject(with: content, options: .allowFragments) as? NSDictionary
{
completion(object, error)
}
else
{
// TODO: Create custom error.
completion(nil, nil)
}
}
catch
{
// TODO: Create custom error.
completion(nil, nil)
}
}
else
{
completion(nil, error)
}
}
}).resume()
}
This function returns the dictionary or an error (after you resolve the TODO's). The completion
is called on the main thread assuming you'll use the result to update the UI. If not, you can remove the DispatchQueue.main.async { }
.

- 2,409
- 3
- 24
- 44
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.didDetectCountryCode = { countryCode in
print(countryCode)
}
controller.loadViewIfNeeded()
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:
- Don't ask for user permissions ideally
- Device locale was not considered a reliable alternative
- Detecting the sim carrier actually returns the original carrier country and not the current connected carrier (via roaming)
- Retrieving the country by IP can be easily faked using VPNs which are becoming more popular lately

- 979
- 6
- 13
-
-
1yes, we passed the review. I am not using any private API, so I don't see any reason why apple would reject this. – crcalin Oct 19 '22 at 11:03
-
I guess they could reject by reason of collecting geo data without permission, or something like this. Thank for answering me! I'll try to implement your logic and hope there will be no problem with review. – nastassia Oct 19 '22 at 12:31