check app settings using userDefaults in iOS using Swift - ios

How do I check what the user sets app settings to? I think I'm supposed to be able to use UserDefaults to do this. I particularly want to check whether the user has allowed the app to access Contacts. I know how to get the setting if I knew the key. I would like to be able to access all the settings that UserDefaults have for the app. I am using Swift to create an iOS app.

You are misunderstanding what the UserDefaults class does. It has no knowledge of the user's settings. It's used for you to save a user's settings (from within your app) persistently.
As for how to access that kind of information. You have to use each of the APIs to check for permissions. For example, access to the GPS requires you to use LocationManager API and check the permission there.
To check if a user has given access to Contacts you have to use the Contacts API (link here)
More specifically this method:
class func authorizationStatus(for entityType: CNEntityType) -> CNAuthorizationStatus
EDIT:
You should really read on what the UserDefaults class is for. (link here)

You can't use UserDefaults to check whether the user has allowed the app to access Contacts. Try the following code.
let status = CNContactStore.authorizationStatus(for: CNEntityType.contacts)
switch (status) {
// Not determined or restricted (e.g.Parent control)
case CNAuthorizationStatus.notDetermined,CNAuthorizationStatus.restricted:
// Denied
case CNAuthorizationStatus.denied:
// Allowed
case CNAuthorizationStatus.authorized:
}

Related

HKHealthStore request authorization once again

In the previous version of my iOS app I was asking users to grant access to write Dietary Energy in Health app (see the code bellow):
let typesToWrite: Set<HKSampleType> = [HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.dietaryEnergyConsumed)!]
healthStore.requestAuthorization(toShare: typesToWrite , read: nil) { (success, error) in
//...
}
And many users gave access to write Energy Data to Health app.
Now, I want to make an update for my app, and I need to add another sample type: Dietary Sugar, and I would like to show again the Health access window, in order to old users gave access to the Dietary Sugar too. The problem is that this window is not showing anymore for old users. How can I force a request authorization for new added type in update version of the app? Thank you.

how to access a sound file stored in another app in swift?

this question is a lot like Share data between two or more iPhone applications except:
I'm looking for a way to do this in an iOS 8+ (or 9+) application using swift
I want to be able to use the sound file contained in the first app so the easter egg I'm making is only available when the user has both apps installed
since this is part of an easter egg, i don't want to use any method that would cause anything extra to be displayed on screen such as a browser redirect or some kind of permission popup
(this basically rules out using the share sheet and custom url's to pass data as described in the post above)
I am using AVAudioPlayer and AVAudioSession in the first app to play the sound if that is at all helpful.
Use App Group
You can share actual NSData through the NSUserDefaults:
if let userDefaults = NSUserDefaults(suiteName: <group>) {
userDefaults.setObject(obj, forKey: key)
}
and retrieve from another app in the same group like so:
if let userDefaults = NSUserDefaults(suiteName: <group>) {
if let obj = userDefaults.objectForKey(key) {
// magic
}
}
It appears that the only limitation for passing data through the user defaults is the device storage capacity, and since NSUserDefaults accepts NSData as a storage format, it makes a prime candidate for sharing tidbits information.
If both apps are yours you can implement a custom url scheme in the second app, and then from the first app ask if it knows how to open an URL with that scheme. If the answer is yes, the app is installed. The function is called canOpenURL. It's an instance method of UIApplication.
I vaguely remember that in iOS 9 and later, Apple added a restriction that you have to register the URLs you are going to ask about in your info.plist, but I don't remember the details. That won't prevent this scheme from working, but it is an extra step you have to take.

SecItemCopyMatching for Touch ID without passcode fallback

I am using SecItemCopyMatching to fetch a keychain item protected by Touch ID.
However, if Touch ID unlocking fails (or the user selects "Enter Passcode"), I want to present my own PIN-entry UI.
I do not want the user to be presented with the system passcode entry UI at any point.
LAContext's evaluatePolicy method provides this, but does not offer any actual keychain security, merely local authentication.
I therefore will not use LAContext to achieve this. Is this possible with SecItemCopyMatching?
On iOS 8.3 and above, the passcode fallback option is hidden initially but still appears if the first finger presented is not recognised.
For iOS 9, two new policies have been added that do not fallback to passcode. These policies are kSecAccessControlTouchIDAny and kSecAccessControlTouchIDCurrentSet
We had similar dilemma while working on one of our in-production app. We realised that we need touch ID unlock as well as custom fallback mechanism (which requires server API for unlocking) which is stronger than 4 digit unlock password.
So, Let me try to explain how we achieve it.
Similar is expectedly done by Apple for Appstore purchase and 1Password app.
Background:
Two mechanisms to integrate Touch ID:
Use Touch ID to access credentials stored in the keychain
Issue:
If a device has Touch ID as well, the preferred method is to authenticate with Touch ID and passcode is the backup mechanism
No other fallback mechanism is permitted and Apple does not allow customisation of the fallback user interface
Use Touch ID to authenticate with the app directly (called Local Authentication)
Issue:
No permission is granted to store secrets into or retrieve secrets from the Secure Enclave
Contrary to the keychain access case, Apple does not allow device passcode authentication as a backup
Every application needs to provide its own fallback to handle failed Touch ID case with custom UI
Concern:
About storing sensitive information in the keychain:
We were tempted to use this approach but were taken aback by realising the only fallback for failing to authenticate with Touch ID is the device passcode. iOS users usually configure a four digit passcode, which is less secure than users custom passwords.
Facelifting examples:
Apple uses your iCloud account password [custom fallback mechanism] as a fallback mechanism for itunes store purchase if user fails to authenticate with Touch ID.
1Password app also has similar approach.
Conclusion
In our app we authenticate with Touch ID via LocalAuthentication, we use our 'app specific PIN unlock feature' or the client's password as the fallback mechanism.
We don't store the password on the device, failure to authenticate with Touch ID requires full authentication through servers API, if device does not have a PIN configured within app.
Sample code:
[self.laContext evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics
localizedReason:reason
reply:^(BOOL success, NSError *error) {
if (success)
dispatch_async(dispatch_get_main_queue(), ^{ successBlock(); });
else
dispatch_async(dispatch_get_main_queue(), ^{ fallbackBlock(error); });
self.laContext = nil;
}
];
This should probably be a comment to bllakjakk, but my reputation does not allow me to do so yet.
I am only adding this answer because the question was specifically asking about:
fetch a keychain item protected by Touch ID
The accepted answer mentions a scenario where no credentials are stored on the device.
For completeness I wanted to mention that if your app needs to authenticate to some external entity, and you therefore have to store credentials somewhere, then the Key-Chain option is the one to consider over Local Authentication.
The fear of somebody being able to authenticate via fallback for Key-Chain if they know the device pass-code (potentially weak four-digit and so on) is a moot point because nothing keeps a user from adding their own fingerprint to the device if they possess the pass-code, and then authenticating via Key-Chain or Local Authentication without having to select the fallback.
So if you are using Local Authentication over Key-Chain because you feel that the fallback mechanism is safer, you might be overlooking that minor detail and therefore be passing up on the opportunity to store credentials in the secure enclave.
We are about to implement TouchID for a financial application and are opting for Key-Chain. The intent is to educate our users about the need for strong device pass-codes at the time when they try to enable TouchID for our app.
I hope this helps you in making a decision.
You can try hiding the Enter Password button by doing the following:
1) define global function
static bool new_isFallbackButtonVisible(id self, SEL _cmd)
{
return NO;
}
2) in your application:didFinishLaunchingWithOptions: replace isFallbackButtonVisible method of LAContext class with your new implementation by calling
class_replaceMethod(NSClassFromString(#"LAContext"), NSSelectorFromString(#"isFallbackButtonVisible"), (IMP)new_isFallbackButtonVisible, "v#:B");
There is no way to disable fallback mechanism using passcode in Keychain TouchID integration. Use LocalAuthentication instead (But LocalAuthentication just provides a TouchID auth UI though not related to the Keychain).
You can hide/customize the "Enter Password" option by setting:
LAContext *context = [[LAContext alloc] init];
context.localizedFallbackTitle = #"";
and the option will disappear, or:
LAContext *context = [[LAContext alloc] init];
context.localizedFallbackTitle = #"Disable TouchID";
to customize the option text. While I know this isn't exactly what the OP was asking, it's most certainly related and could "fallback" into wanting minds.

iOS: Detect whether my SDK is installed on another apps on the device

I am developing a location based Q&A SDK for mobile devices.
When a question is asked about a specific location, the server side targets the most relevant user and sends the question to that user. If the user fails to answer, the question is sent to the second best user, and so on.
The problem is that my SDK might be installed on more than one application on the device, meaning that the user can get a question more than once.
Is there a way to detect whether my SDK is installed on more than one app? I thought that sending the UDID to the server might work, but iOS UDIDs differ between applications.
You can use UIPasteboard to share data between applications on the device.
The UIPasteboard class enables an app to share data within the app and with another app. To share data with any other app, you can use system-wide pasteboards; to share data with another app that has the same team ID as your app, you can use app-specific pasteboards.
In your SDK, do something like this:
#interface SDKDetector : NSObject
#end
#implementation SDKDetector
+ (void)load
{
int numberOfApps = (int)[self numberOfAppsInDeviceUsingSDK];
NSLog(#"Number of apps using sdk:%d", numberOfApps);
}
+ (NSInteger)numberOfAppsInDeviceUsingSDK
{
static NSString *pasteboardType = #"mySDKPasteboardUniqueKey";
NSData *value = [[UIPasteboard generalPasteboard] valueForPasteboardType:pasteboardType];
NSMutableArray *storedData = [[NSKeyedUnarchiver unarchiveObjectWithData:value] mutableCopy];
if (!storedData) {
storedData = [NSMutableArray new];
}
NSString *bundleId = [[NSBundle mainBundle] bundleIdentifier];
if (![storedData containsObject:bundleId]) {
[storedData addObject:[[NSBundle mainBundle] bundleIdentifier]];
}
value = [NSKeyedArchiver archivedDataWithRootObject:storedData];
[[UIPasteboard generalPasteboard] setData:value forPasteboardType:pasteboardType];
return [storedData count];
}
#end
If you only want to provide an SDK, it is not possible. Apple has added security steps to prevent that for user privacy. Keychain sharing will not work, because apps must share the same bundle seed ID (see here for more info).
If you want to provide an app along with your SDK, then you could do something like Facebook does, where app sends a "helo" message, Facebook asks user and finally Facebook sends "ehlo" message.
Your App -> "I would like to use the SDK; please give me token" -> SDK Controller App -> (Remember which apps have requested use) -> "OK, you can use the SDK; here is a token: #123" -> Your App
The SDK controller app can now send the server the list of apps.
I think you can group the apps on the same device by IP address as they will use the same address to connect to your server.
So the IP address will represent the device and the API key will represent the app that uses the SDK.
Can you try using
advertisingIdentifier
Not sure whether it serves your purpose. It is explained in here ASIdentifierManager class reference : Apple doc
I think its possible using keychain, you can have an unique keychain key in which you can save anything you want, and can be accessed by other apps if available. So for your SDK, lets say if there is one app, it will register some value in keychain with a unique key which is private to your SDK only if the key doesn't exist, and if it exist you get to know, since you can save any value in keychain, you can try multiple options and combinations which suits you.
You can use KeychainItemWrapper for the implementations.
Elaboration
Lets say we have an method.
[MySDK register];
Which can be used anywhere, say in AppDelegate. The register method will generate a token for the app, for the device, which we will save in the keychain using an unique key we have defined in the SDK, say in com.mysdk.key. And while saving in keychain the SDK can actually do a registration.
We consider the above method is implemented in multiple apps.
Now we have scenario.
User installs an App-A which uses the SDK, the register method will call and create a token and will save in keychain for the first time.
Now user installs another App-B which also uses the SDK, the same register method will call, but now it will check for the key com.mysdk.key in keychain, if exist it will just update the count for the token, which meant for the device.
Note
Keychain not meant to save only unique identifier, you can save other informations too.
Update
Check demo projects https://db.tt/7xpKrgMp
The wrapper I have used in the projects is same as SDK in your case which is same in both the projects.
Cheers.

How Can I check programmatically app store region/country?

Is there a way to check it?
I have an application URL, which I don't want to be opened expect if the user have a uk appstore. unfortunately, this application is available in many country, so when I put 'gb' on the link, it be redirected to the local region of the user.
For iOS 13+, check the SKStoreFront class. It has a countryCode that returns the country code belonging to the user's current App Store region.
Swift
if let storefront = SKPaymentQueue.default().storefront {
print(storefront.countryCode) // Returns an Alpha-3 country code (USA, GBR etc.)
}
Obj C
[SKPaymentQueue defaultQueue].storefront.countryCode; // Returns an Alpha-3 country code (USA, GBR etc.)
You should be able to access the SKStoreFront instance by adding the StoreKit framework to your project, even if your app does not offer any purchases.
For more information, check out: https://developer.apple.com/documentation/storekit/skstorefront
You could use the in-app purchase Store Kit to achieve this.
Request the product list using SKProductsRequest then check the returned SKProduct's priceLocale to see if the user's AppStore is the UK one.

Resources