Detect user’s country without accessing their location

Sometimes we are faced with challenge where we would like to improve user experience of our app based on where in the world or better said in which country our clients are using the application.

For example if you would like to show some content (or even download in from the API on the fly), that is specific just for that country and has no meaning to show it to clients located in other countries.

I will show you how to do this without using CoreLocation.

Common solution

Usually when we use location services it is pretty straight forward task since you can receive clients location through CoreLocation. But for CoreLocation to work you need to handle at least few things before you can actually (try to) read location.

  • Add keys with authorisation request purpose strings to your app’s Info.plist file
  • Execute authentication request
  • Get location with one of services that are available and read country from that data
  • Handle all scenarios where user denies location access

As mentioned before, this might be solution that you are looking for and works for you since you are already using CoreLocation and makes sense to the app itself.

But with rise of privacy awareness and if you application is not location based app, clients can get confused when they see big popup all over the screen with message that application would like to access their location. It can be a big “no no” for some clients and they can just deny your request and even delete the app. This is something that we definitely don’t want to happen.

Alternative solution

Thankfully there is an alternative way to achieve same result. Apple provided us Core Telephony framework which can deliver you information about client’s cellular service provider.

Use the Core Telephony framework to obtain information about a user’s home cellular service provider. Carriers can use this information to write apps that provide services only for their own subscribers. You can also use this framework to obtain information about current cellular calls.

So how it can help us? One thing that it has is CTCarrier object. It contains standard informations that are available regarding cellular service provider such as its unique identifier and whether it allows VoIP calls on its network. But there is one special property that can help you – isoCountryCode.

Since country code is based on ISO 3166-1 standard we can easily use it as information for determining in which country your client is.

One thing to keep in mind is that isoCountryCode is optional which means that there is no guarantee that it will be always available for you. Situations when code is nil are:

  • The device is in airplane mode.
  • There is no SIM card in the device.
  • The device is outside of cellular service range.

Examples

And how do you implement this? Simplest solution would be something like this.

import CoreTelephony

class CountryCodeLoader {
    func load() -> String? {
        guard
            let providers = CTTelephonyNetworkInfo().serviceSubscriberCellularProviders,
            let cellularProvider = providers.first(where: { $0.value.isoCountryCode != nil })?.value,
            let isoCountryCode = cellularProvider.isoCountryCode
        else {
            return nil
        }
        return isoCountryCode
    }
}

Of course we can expand this by making dedicated model which holds information about detected code. One way would be by using simple struct.

import CoreTelephony

struct Country {
    let isoCode: String
}

class CountryLoader {
    func load() -> Country? {
        guard
            let providers = CTTelephonyNetworkInfo().serviceSubscriberCellularProviders,
            let cellularProvider = providers.first(where: { $0.value.isoCountryCode != nil })?.value,
            let isoCountryCode = cellularProvider.isoCountryCode
        else {
            return nil
        }
        return Country(isoCode: isoCountryCode)
    }
}

Or even an enum representation of the countries if you want to check if country is one of those that application supports.

import CoreTelephony

enum SupportedCountry: String {
    case SI
}

class SupportedCountryLoader {
    func load() -> SupportedCountry? {
        guard
            let providers = CTTelephonyNetworkInfo().serviceSubscriberCellularProviders,
            let cellularProvider = providers.first(where: { $0.value.isoCountryCode != nil })?.value,
            let isoCountryCode = cellularProvider.isoCountryCode
        else {
            return nil
        }
        return SupportedCountry(rawValue: isoCountryCode.uppercased())
    }
}

Gotchas

Although this looks pretty simple and easy solution there is one thing that you need to be aware of. IsoCountryCode returns the country code that belongs to the issuer of the SIM card, not the country code of the country the client is currently in. So if client is currently abroad / in another country, the country code that you get may not be what you expect.

For example if your client is using Slovenian SIM card and is currently in UK while using Slovenian SIM card you will receive ISO country code for Slovenia.

Conclusion

This approach has some limitations but it is good to know that there is an alternative way to retrieve country information when precise location / location tracking is not our interest and using CoreLocation is not an option for your product.

And that’s it. We have looked at an alternative way of how to get country of your client. I hope you enjoyed the article and learned something new.

In case of any questions or comments feel free to contact me or leave it in the comments section bellow.

Architectural Patterns – Decorator

/

In this article I will present you powerful architectural pattern that I have been using it a lot called Decorator.

1. What is Decorator

...

DDD – Aggregates part I.

/

Welcome to the fourth article about Domain Driven Design. In the previous article, we learned about one of the primary concepts in Domain Driven DesignEntities. This time, ...

Subscribe
Notify of
guest

2 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
antonio081014
antonio081014
2 years ago

Good one.