iOS - Knowing app store country - ios

I want to know in my app (by code) in which app store my user is (like england / france / spain ect).
I already read that we can do this with the locale :
https://developer.apple.com/documentation/foundation/nslocale/1643060-countrycode
But I would like to do it with the Apple Store. For legal purpose I don't want to display the same content for an european than for an american.
Has someone already done it ? Thanks !

From iOS13 SKStorefront class property countryCode can be used to get three-letter code representing the country associated with the App Store storefront.
For iOS version below 13 only viable solution was to get priceLocale from SKProduct.

You can't restrict IAP(so you don't have information about the Apple Store used) for specific country.
What you can do is disable/enable items by checking country ID.
there are different way for check it, for me the best is by checking user carrier ID.
For example:
func checkCellularNumber() -> Bool {
let networkInfo = CTTelephonyNetworkInfo()
guard let info = networkInfo.subscriberCellularProvider else {return false}
if let carrier = info.isoCountryCode {
print("Carrier code = \(carrier)");
return true
}
return false
}

If you're on iOS 13+, This will give you the 3 letter country code for the store:
import StoreKit
let country = SKPaymentQueue.default().storefront?.countryCode
More information on it's usage can be found in the SKStoreFront documentation.
UPDATE:
Occasionally, this method returns nil, which is why storefront is an optional. So it's not 100% reliable. I was using with thousands of users, and it was working 95% of the time. I'm not entirely sure under what circumstances it is nil however.

Related

can we release same app with 1 or 2 different functionality for 2 different countries?

I want to release my app in only 2 country and i want to do 2 different functionality for both country.
For example.
ViewController1 functionality is different in Jamaica.
ViewController1 functionality is different in Kenya.
Different functionality means content is different, or input forms are different.
Is it possible? if yes then please refer some document.
Thanks in advance
You should have a screen that allows user to select their country, after that, store selected country in our app (by UserDefault or Keychain, etc...).
Based on the selected country then you can switch logic/layout to adapt the requirement above
some notes about App Store:
1) language should / must be selected by user on Prefs, NOT in Apps.
Chances Apple will refuse apps not following above logic.
2) You could test current language / Zone using code (see below for language)
BUT I think Apple can refuse as you use a different behaviour
3) if really you need it, You can load a different controller using Storyboards (I suggest using different storyboards AND lod them at runtime using segues and "*.soryboard" as in:
func ViewControllerFromStoryboardWith( name: String ) -> UIViewController {
// we use an identifier equal to filename for now.
let storyboard = UIStoryboard(name: name, bundle: nil)
let vc = storyboard.instantiateViewController(withIdentifier: name) as UIViewController
return vc
}
// test lang:
func currHWLanguage()->String{
let defs : UserDefaults = UserDefaults.standard
let languages : NSArray = defs.object(forKey: "AppleLanguages") as! NSArray
let current = languages[0] as! String
// since 9.0 we get en-US etc.. so cut to 2:
let result = (current as NSString).substring(to: 2)
#if DEBUG
// force to IT as a bug in simulator
// return "IT"
#endif
return result.uppercased()
//NSLog("%#", current)
}
This is a problem many applications are trying to solve. Basically, you have the following options:
Let the user choose. This is the safest option if one application contains two different configuration.
Try to detect location of the user. Language/Locale is unsafe because many people will have English (or different) locale set up. Very unsafe. You shouldn't ask for GPS location for this. The safest option is to create a server request and check the location using the IP address. A bit complicated and won't work if a VPN is used (e.g. antivirus apps create VPNs).
Create two different apps. In the end, this is the best option. Add a second application target to your project and release two separate apps with separate configuration.

Disabling Callkit from China Store Best Approach?

We are using CallKit framework to benefit native usage for Voip features. Users can make Voice and Video Calls in our Messenger App.
But Apple removing CallKit apps from China, because of Chinese government.
What is the best approach for CallKit apps like us for now?
We do not want to remove our app from China and we do not remove all CallKit functionality from our app because of China..
I agree with txulu that it seems that CallKit just needs to be disabled/not used for users in China - see this helpful response on the Apple Developer forums.
The general consensus seems to be that as long as you can explain to App Review how you’re disabling CallKit features for users in China, that should probably be acceptable unless/until Apple publishes specific guidelines.
For your particular problem Ahmet, it sounds like CallKit may provide some of the the core functionality of your app. If this is the case and you really need to support users in China, you might want to look at rebuilding your app using another VOIP framework to make calls (VOIP is still allowed in China...just not using CallKit). Or perhaps you could disable and hide the calling features in your app if the user is in China.
My app was only using CallKit to observe when a call initiated from my app ends, so I was able to devise a work around. For users in China I now observe for the UIApplicationDidBecomeActiveNotification and make my best guess about whether a phone call initiated from the app has ended based on how much time has elapsed since the call began. It's not as good as using CallKit's CXCallObserver, but it seems to work well enough for my purpose.
Update! My app passed App Store review with the fix described.
Submitted a new version yesterday.
Included a short message in the reviewer info section saying "In this version and onwards, we do not use CallKit features for users in China. We detect the user's region using NSLocale."
App was approved around 12hr later without any questions or comments from the App Review team.
Detecting users in China
To determine if a user is in China, I am using NSLocale to get the users' currentLocale and countryCode. If the countryCode contains one of the ISO codes for China (CN, CHN), I set a flag to note I cannot use CallKit and not initialize or use CallKit features in my app.
- (void)viewDidLoad {
[super viewDidLoad];
NSLocale *userLocale = [NSLocale currentLocale];
if ([userLocale.countryCode containsString: #"CN"] || [userLocale.countryCode containsString: #"CHN"]) {
NSLog(#"currentLocale is China so we cannot use CallKit.");
self.cannotUseCallKit = YES;
} else {
self.cannotUseCallKit = NO;
// setup CallKit observer
self.callObserver = [[CXCallObserver alloc] init];
[self.callObserver setDelegate:self queue:nil];
}
}
To test this, you can change the region in Settings > General > Language and Region > Region. When I set Region to 'China' but left language set as English, [NSLocale currentLocale] returned "en_CN".
Swift 5
Utility Functions
func isCallKitSupported() -> Bool {
let userLocale = NSLocale.current
guard let regionCode = userLocale.regionCode else { return false }
if regionCode.contains("CN") ||
regionCode.contains("CHN") {
return false
} else {
return true
}
}
MainViewController
class MainViewController: UIViewController {
...
var callObserver = CXCallObserver()
...
override func viewDidLoad() {
super.viewDidLoad()
if isCallKitSupported() {
callObserver.setDelegate(self, queue: nil)
}
...
}
...
}
Note: countryCode is now regionCode and only returns 'US', 'CN', etc. No language before country code like 'en_CN'.
Swift 5
func isCallKitSupport() -> Bool {
let userLocale = NSLocale.current
if userLocale.regionCode?.contains("CN") != nil ||
userLocale.regionCode?.contains("CHN") != nil {
return false
} else {
return true
}
}
One thing you could try, even though it may not work: disable callkit functionality based on the locale region. This may be enough "proof" that Callkit is disabled for China from the legal perspective in order to be approved for the Appstore. Then your Chinese customers could just switch the region in the settings to get Callkit. This would be already "their" problem so to speak.
Disclaimer: I'm by no means a lawyer or anything, follow this advice at your own risk.
Edit:
CXProvider.isSupported is no longer available: I keep the answer here hoping that it will be restored back on an upcoming iOS 13 release.
From iOS 13 onwards, the correct way to do this is to check the new CXProvider.isSupported property.
Here's the documentation (from Xcode, as the online documentation has not been updated yet):
Go to “Pricing and Availability” in iTunes Connect.
Availability” (Click blue button Edit).
Deselect China in the list “Deselect” button.
Click “Done”.

A loop to continuously collect the Wifi strength of nearby access points

Assume I have an iPhone connected to a wifi network with 3+ access points.
I'd like to collect all possible fields around wifi access strength/signal/etc from EACH access point and use that to triangulate, even while in background.
while true {
...
for access_point in access_points {
...
signal_strength = ...
}
}
I've been reading previous SO answers and other posts, and seems like it wasn't allowed on iOS without a jailbreak for a while, but is now availiable again.
Anyone can show a code snippet of how I'd go about doing this? All new to iOS development..
It's been quite a while since I worked with this, so I did a quick check again and now I am fairly certain you misunderstood something you've read. As far as I can tell, Apple did not suddenly revert their previous decision to restrict the public frameworks to scan for access points, i.e. specific MAC addresses and their signal strength.
You can query the specific rssi (signal strength) for a network (i.e. for an ssid), but not for individual MAC addresses. Before iOS 5 you could do that using private APIs, then you could do it with private APIs on a jailbroken device and that's pretty much it.
I don't have the code of my own, old stuff at hand (I used to do this for indoor location tracking before we switched to use iBeacons), so I can't provide you with a sample snippet myself. My code is dated and no longer functioning anyways, but you might find something here.
I would be really interested in the sources you mention that claim iOS 10 now allows this again. Apple closed this for privacy considerations (officially at least, and although this might be true in part it also means developers dealing with location-tracking now need to rely fully on Apple's framework for that only), so I highly doubt they went back on it.
Also, note that this is for sure not something trivial, especially if you're new to iOS development. I haven't even tackled the background idea, you can safely forget about that, because no matter what you do, you will not have a scanner that runs continuously in the background. That's against a very core principle of iOS programming.
I've answered how to ping ALL wifi networks in this question;
func getInterfaces() -> Bool {
guard let unwrappedCFArrayInterfaces = CNCopySupportedInterfaces() else {
print("this must be a simulator, no interfaces found")
return false
}
guard let swiftInterfaces = (unwrappedCFArrayInterfaces as NSArray) as? [String] else {
print("System error: did not come back as array of Strings")
return false
}
for interface in swiftInterfaces {
print("Looking up SSID info for \(interface)") // en0
guard let unwrappedCFDictionaryForInterface = CNCopyCurrentNetworkInfo(interface) else {
print("System error: \(interface) has no information")
return false
}
guard let SSIDDict = (unwrappedCFDictionaryForInterface as NSDictionary) as? [String: AnyObject] else {
print("System error: interface information is not a string-keyed dictionary")
return false
}
for d in SSIDDict.keys {
print("\(d): \(SSIDDict[d]!)")
}
}
return true
}
You may have seen this feature in jailbroken apps as it is possible to do this using private libraries, which means that apps that are sold on the iOS store can't be sold if they utilise them.

How can I get details about the device data provider (like Verizon/AT&T) of an iphone programmatically?

I am trying to create an ios application and I want to segment the users based on the data providers they are using, such as Verizon and AT&T. Is it possible to get this information programmatically from the ios application.
You should check the CTCarrier.
Just import CoreTelephony into your Swift file.
Then you can use the carrierName property to get the name of your carrier.
// Setup the Network Info and create a CTCarrier object
let networkInfo = CTTelephonyNetworkInfo()
let carrier = networkInfo.subscriberCellularProvider
// Get carrier name
let carrierName = carrier.carrierName
Now that you can have multiple SIM cards, subscriberCellularProvider is deprecated in favor of serviceSubscriberCellularProviders. You can get an array of the providers with this:
let carriers = CTTelephonyNetworkInfo().serviceSubscriberCellularProviders?.values
In my case, I was checking to see if the user has an American phone number so they can text support instead of email. You can do that with this:
carriers.contains { $0.isoCountryCode?.lowercased() == "us" }
On my phone, I only have one SIM card, but this array returns two values and one has all nil properties so be sure to handle that if you are inspecting them.
You will want to use the CTCarrier carrierName in the CoreTelephony framework: https://developer.apple.com/library/prerelease/ios/documentation/NetworkingInternet/Reference/CTCarrier/index.html#//apple_ref/occ/instp/CTCarrier/carrierName

iOS 7+: Unique identifier per device<->Store-ID (user) combination

I've spent two days no reading and testing as there is a lot of info about this topic.
Unfortunately I've found no solution yet. I can't implement my own authentication as this doesn't help with the issue I want to solve (see Backgrounding at the end of the question).
Here is my current best approach:
I'm generating a UUID thanks to https://stackoverflow.com/a/8677177/1443733 and storing it in the KeyChain as suggested with SwiftKeychainWrapper (https://github.com/jrendel/SwiftKeychainWrapper)
The short nice and sweet code for that is:
let stored = KeychainWrapper.stringForKey("UUID")
if stored != nil {
Helper.log(TAG, msg: "retrieved from keychain: \(stored!)")
} else {
let theUUID = CFUUIDCreate(nil)
let str = CFUUIDCreateString(nil, theUUID)
Helper.log(TAG, msg: "generated UUID: \(str)")
let ret = KeychainWrapper.setString(str, forKey: "UUID")
Helper.log(TAG, msg: "setkeychain: \(ret)")
}
But the UUID stored in the keychain seems to be per device and not per store ID as well.
When I store the UUID like above and login with a different Store ID on the device KeychainWrapper.stringForKey("UUID")still returns the value of the other user.
Isn't their a way to store a value in a store-id keychain?
It seems that I'm so close so I hope someone could point me in the right direction.
If that approach (with a keychain) can't succeed please let me know as well.
I reckon you can ask a different question as well: Is there some cryptic data I can read/generate or a store which changes with the Store Id currently used on a device?
Ohh... and Swift examples preffered ;)
Backgroundinfo:
I use IAPs in my app and want to save the Store-Id of the user once a refresh of the receipt is valid.
On each start of the app I check if the current Store-Id is the same as the saved one. If not I trigger immediately a refresh of the receipt. If it fails I fall back to the free version of the app.
iOS devices do not support multiple users.
If you want to differentiate between users you will have to do that in your app, perhaps with a user login. Then save a UUID per userID in the Keychain.
As NSUserdefaults temporarily stores the UUID.so,after unistalling the app and again installing,the UID changes. So, UUID must be stored in keychain

Resources