I write SDK for iOS and I want to validate if StoreKit.framework is linked to application that uses my SDK, so I run:
if ([SKStoreProductViewController class]) {
SKStoreProductViewController *storeController =
[[ SKStoreProductViewController alloc ] init ];
// ...
}
However even if StoreKit.framework is not linked [SKStoreProductViewController class still returns true.
How to solve this problem?
Edit 1
as #x4h1d pointed I created new empty project and added to default Controller:
BOOL isStoreKitAvailable =
(NSClassFromString(#"SKStoreProductViewController") != nil);
// => YES (there is no linked frameworks at all, why I get YES?)
Edit 2
My Provisioning profile has In-App Purchase enabled (not a project itself)
from iOS App IDs:
However from Xcode:
Maybe this is a reason why even empty application has build-in StoreKit?
You can check the storekit availibility using following code.
func checkStoreKitAvailibility() -> Bool {
for bundle in Bundle.allFrameworks {
if ((bundle.classNamed("SKStoreProductViewController")) != nil) {
return true;
}
}
return false;
}
Edit:
For Objective-C you can use:
- (BOOL)checkStoreKitAvailibility {
for (NSBundle *bundle in NSBundle.allFrameworks) {
if ([bundle classNamed:#"SKStoreProductViewController"]) {
return YES;
}
}
return NO;
}
In the Linked Frameworks and Libraries make it Optional instead of Required. So, that if the application developers wants it to implement he will mark the framework as Required in the application. Now if you use the [SKStoreProductViewController class]. it may crash use the NSStringFromClass(#"SKStoreProductViewController") to determine if its safe to use it.
In Xcode,
when we enable the In-App purchase under Capabilities, Xcode automatically links StoreKit.framework and Add the In-App purchase feature to our App ID. Similarly if our App ID already have In-App purchase enabled the same happens.
So by doing simply,
BOOL isStoreKitAvailable = (NSClassFromString(#"SKStoreProductViewController") != nil);
I hope this might help you.
//SWIFT
class func allFrameworks() -> [AnyObject]
//OBJECTIVE-C
+ (NSArray *)allFrameworks
From NSBundle
+allFrameworks
Returns an array of all of the application’s bundles that represent frameworks.
Return Value
An array of all of the application’s bundles that represent frameworks. Only frameworks with one or more Objective-C classes in them are included.
Import Statement
import Foundation
Availability
Available in iOS 2.0 and later.
How to Use
(NSBundle.allFrameworks() -> Return Array of All framework use in project.
You can Check Using for loop apllication contain storeKit.framework or Not as Below.
Swift :---
func isStoreKitAvailable() -> Bool {
for frameWorkName in Bundle.allFrameworks {
if ((frameWorkName.classNamed("SKStoreProductViewController")) != nil) {
return true;
}
}
return false;
}
Objective C :---
- (BOOL)isStoreKitAvailable {
for (NSBundle *frameWorkName in NSBundle.allFrameworks) {
if ([frameWorkName classNamed:#"SKStoreProductViewController"]) {
return YES;
}
}
return NO;
}
Find more reference from here
I'm currently developing an iOS app that enables users to log in to the app using TouchID, but firstly they must set up a password inside the app first. Problem is, to show the setup password option to enable the TouchID login, I need to detect if the iOS device supports TouchID.
Using the LAContext and canEvaluatePolicy (like the answers in here If Device Supports Touch ID), I am able to determine whether the current device supports TouchID if the user has set up passcode on their iOS device. Here is a my code snippet (I'm using Xamarin, so it's in C#):
static bool DeviceSupportsTouchID ()
{
if (UIDevice.CurrentDevice.CheckSystemVersion(8, 0))
{
var context = new LAContext();
NSError authError;
bool touchIDSetOnDevice = context.CanEvaluatePolicy(LAPolicy.DeviceOwnerAuthenticationWithBiometrics, out authError);
return (touchIDSetOnDevice || (LAStatus) Convert.ToInt16(authError.Code) != LAStatus.TouchIDNotAvailable);
}
return false;
}
If the user has not set up the device passcode, the authError will just return "PasscodeNotSet" error regardless of whether the device actually supports TouchID or not.
If the user's device supports TouchID, I want to always show the TouchID option in my app regardless of whether the user has set up passcode on their device (I will just warn the user to setup passcode on their device first). Vice versa, if the user's device doesn't support TouchID, I obviously don't want to show the TouchID option in my app.
So my question is, is there a nice way to consistently determine whether an iOS device supports TouchID regardless of whether the user has set up passcode on their device?
The only workaround I can think of is to determine the architecture of the device (which is answered in Determine if iOS device is 32- or 64-bit), as TouchID is only supported on devices with 64-bit architecture. However, I'm looking if there's any nicer way to do this.
In conclusion of the discussion below, for the time being it is not possible to determine whether a device actually supports TouchID or not when the user hasn't set up passcode on their device.
I have reported this TouchID flaw on the Apple bug reporter. Those who want to follow the issue can see it on Open Radar here: http://www.openradar.me/20342024
Thanks #rckoenes for the input :)
EDIT
Turns out that someone has reported a similar issue already (#18364575). Here is Apple's reply regarding the issue:
"Engineering has determined that this issue behaves as intended based on the following information:
If passcode is not set, you will not be able to detect Touch ID presence. Once the passcode is set, canEvaluatePolicy will eventually return LAErrorTouchIDNotAvailable or LAErrorTouchIdNotEnrolled and you will be able to detect Touch ID presence/state.
If users have disabled passcode on phone with Touch ID, they knew that they will not be able to use Touch ID, so the apps don't need to detect Touch ID presence or promote Touch ID based features. "
So..... the final answer from Apple is No. :(
Note: similar StackOverflow question from the person who reported this -> iOS8 check if device has Touch ID
(wonder why I didn't find this question before despite my extensive searching...)
The correct way to detect if TouchID is available:
BOOL hasTouchID = NO;
// if the LAContext class is available
if ([LAContext class]) {
LAContext *context = [LAContext new];
NSError *error = nil;
hasTouchId = [context canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&error];
}
Sorry it is in Objective-C, you might have to translate it to C#.
You should refrain from checking the system version and just check whether or not the class or methods are available.
I know this is a question from last year, but this solution does not make what you need? (Swift code)
if #available(iOS 8.0, *) {
var error: NSError?
let hasTouchID = LAContext().canEvaluatePolicy(LAPolicy.DeviceOwnerAuthenticationWithBiometrics, error: &error)
//Show the touch id option if the device has touch id hardware feature (even if the passcode is not set or touch id is not enrolled)
if(hasTouchID || (error?.code != LAError.TouchIDNotAvailable.rawValue)) {
touchIDContentView.hidden = false
}
}
Then, when the user presses the button to log in with touch id:
#IBAction func loginWithTouchId() {
let context = LAContext()
var error: NSError?
let reasonString = "Log in with Touch ID"
if (context.canEvaluatePolicy(LAPolicy.DeviceOwnerAuthenticationWithBiometrics, error: &error)) {
[context.evaluatePolicy(LAPolicy.DeviceOwnerAuthenticationWithBiometrics, localizedReason: reasonString, reply: { (success: Bool, evalPolicyError: NSError?) -> Void in
//Has touch id. Treat the success boolean
})]
} else {
//Then, if the user has touch id but is not enrolled or the passcode is not set, show a alert message
switch error!.code{
case LAError.TouchIDNotEnrolled.rawValue:
//Show alert message to inform that touch id is not enrolled
break
case LAError.PasscodeNotSet.rawValue:
//Show alert message to inform that passcode is not set
break
default:
// The LAError.TouchIDNotAvailable case.
// Will not catch here, because if not available, the option will not visible
}
}
}
Hope it helps!
For Objective C
It works great on all devices without checking device version.
- (void)canAuthenticatedByTouchID{
LAContext *myContext = [[LAContext alloc] init];
NSError *authError = nil;
NSString *myLocalizedReasonString = touchIDRequestReason;
if ([myContext canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&authError]) {
}else{
switch (authError.code) {
case kLAErrorTouchIDNotAvailable:
[labelNotSupportTouchID setHidden:NO];
[switchBtn setHidden:YES];
[labelEnableTouchid setHidden:YES];
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self showAlertMessage:#"EyeCheck Pro" message:#"Device does not support Touch ID Service."];
});
break;
}
}
}
Here is a bit tedious way to figure out if device has physical touch id sensor.
+ (BOOL)isTouchIDExist {
if(![LAContext class]) //Since this mandotory class is not there, that means there is no physical touch id.
return false;
//Get the current device model name
size_t size;
sysctlbyname("hw.machine", NULL, &size, NULL, 0);
char *model = malloc(size);
sysctlbyname("hw.machine", model, &size, NULL, 0);
NSString *deviceModel = [NSString stringWithCString:model encoding:NSUTF8StringEncoding];
//Devices that does not support touch id
NSArray *deviceModelsWithoutTouchID = [[NSArray alloc]
initWithObjects:
#"iPhone1,1", //iPhone
#"iPhone1,2", //iPhone 3G
#"iPhone2,1", //iPhone 3GS
#"iPhone3,1", //iPhone 4
#"iPhone3,2",
#"iPhone3,3",
#"iPhone4,1", //iPhone 4S
#"iPhone5,1", //iPhone 5
#"iPhone5,2",
#"iPhone5,3", //iPhone 5C
#"iPhone5,4",
#"iPod1,1", //iPod
#"iPod2,1",
#"iPod3,1",
#"iPod4,1",
#"iPod5,1",
#"iPod7,1",
#"iPad1,1", //iPad
#"iPad2,1", //iPad 2
#"iPad2,2",
#"iPad2,3",
#"iPad2,4",// iPad mini 1G
#"iPad2,5",
#"iPad2,5",
#"iPad2,7",
#"iPad3,1", //iPad 3
#"iPad3,2",
#"iPad3,3",
#"iPad3,4", //iPad 4
#"iPad3,5",
#"iPad3,6",
#"iPad4,1", //iPad Air
#"iPad4,2",
#"iPad4,3",
#"iPad4,4", //iPad mini 2
#"iPad4,5",
#"iPad4,6",
#"iPad4,7",
nil];
return ![deviceModelsWithoutTouchID containsObject:deviceModel];
}
Reference:
https://www.theiphonewiki.com/wiki/Models
https://en.wikipedia.org/wiki/IOS
Following is the way by which you can identify whether Touch Id or Face ID is supported on the device
open class LocalAuth: NSObject {
public static let shared = LocalAuth()
private override init() {}
var laContext = LAContext()
func canAuthenticate() -> Bool {
var error: NSError?
let hasTouchId = laContext.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error)
return hasTouchId
}
func hasTouchId() -> Bool {
if canAuthenticate() && laContext.biometryType == .touchID {
return true
}
return false
}
func hasFaceId() -> Bool {
if canAuthenticate() && laContext.biometryType == .faceID {
return true
}
return false
}
}
And following is the Usage of the above-shared code
if LocalAuth.shared.hasTouchId() {
print("Has Touch Id")
} else if LocalAuth.shared.hasFaceId() {
print("Has Face Id")
} else {
print("Device does not have Biometric Authentication Method")
}
For iOS 11+ you can use biometryType: LABiometryType of LAContext. More from Apple documentation:
/// Indicates the type of the biometry supported by the device.
///
/// #discussion This property is set only when canEvaluatePolicy succeeds for a biometric policy.
/// The default value is LABiometryTypeNone.
#available(iOS 11.0, *)
open var biometryType: LABiometryType { get }
#available(iOS 11.0, *)
public enum LABiometryType : Int {
/// The device does not support biometry.
#available(iOS 11.2, *)
case none
/// The device does not support biometry.
#available(iOS, introduced: 11.0, deprecated: 11.2, renamed: "LABiometryType.none")
public static var LABiometryNone: LABiometryType { get }
/// The device supports Touch ID.
case touchID
/// The device supports Face ID.
case faceID
}
For iOS 11+, for context error, you can check for kLAErrorBiometryNotAvailable
I was wondering if there is a method that would allow me to detect from the keyboard container app whether the associated keyboard has been activated in the the device's Settings app.
For example, I am interested in adding a simple "steps" feature inside the container app where step 1 would be "activate the keyboard", and step 2 would be contingent on step 1's completion. As such, I am interested in figuring out whether there is a way to detect whether the keyboard extension is activated?
Thanks!
Here is a method I have used in one of my projects. I think it is what you asked for, hope it helps you.
- (BOOL)isCustomKeyboardEnabled {
NSString *bundleID = #"com.company.app.customkeyboard"; // Replace this string with your custom keyboard's bundle ID
NSArray *keyboards = [[[NSUserDefaults standardUserDefaults] dictionaryRepresentation] objectForKey:#"AppleKeyboards"]; // Array of all active keyboards
for (NSString *keyboard in keyboards) {
if ([keyboard isEqualToString:bundleID])
return YES;
}
return NO;
}
Just in case here is Swift version of Kurt's brilliant and awesome answer:
func isKeyboardExtensionEnabled() -> Bool {
guard let appBundleIdentifier = Bundle.main.bundleIdentifier else {
fatalError("isKeyboardExtensionEnabled(): Cannot retrieve bundle identifier.")
}
guard let keyboards = UserDefaults.standard.dictionaryRepresentation()["AppleKeyboards"] as? [String] else {
// There is no key `AppleKeyboards` in NSUserDefaults. That happens sometimes.
return false
}
let keyboardExtensionBundleIdentifierPrefix = appBundleIdentifier + "."
for keyboard in keyboards {
if keyboard.hasPrefix(keyboardExtensionBundleIdentifierPrefix) {
return true
}
}
return false
}
The current documentation states By default, your extension and its containing app have no direct access to each other’s containers.
It is also stating that the container app can share data with the keyboard in the following fashion:
// Create and share access to an NSUserDefaults object.
NSUserDefaults *mySharedDefaults = [[NSUserDefaults alloc]
initWithSuiteName:#"com.example.domain.MyShareExtension"];
// Use the shared user defaults object to update the user's account.
[mySharedDefaults setObject:theAccountName forKey:#"lastAccountName"];
Read more on this: Communicating and persisting data between apps with App Groups
Obstacle no 1: According to the documentation, for this to work, the RequestsOpenAccess in the plist needs to be set to YES as it would gain the following capability:
Option to use a shared container with the keyboard’s containing app,
which enables features such as providing a custom lexicon management
UI in the containing app
Requesting full access for a simple case like this is definitely not preferred on my side.
Obstacle no 2: Using this knowledge of setting a NSUserDefault, leaves me to think of a method where this can be set in place. But there's no public method indicating an extension is installed. So this is a dead end for now.
--
[Update 1]
Not super relevant but still worth stating: the shouldAllowExtensionPointIdentifier app delegate method in combination with the constant UIApplicationKeyboardExtensionPointIdentifier can deal with disallowing custom keyboards. The extension point identifiers are not unique identifiers of the extension but of their type.
Read more on this: Can I disable custom keyboards (iOS8) for my app?
--
[Update 2]
Another question with same issue, but w/o solution: How to detect an app extension is enabled in containing app on iOS 8?
--
This is a work-in-progress answer stating my findings so far which I hope to be updating coming days should I find a solution.
You can use this function (Swift 3 and 4) to check your custom keyboard extension have open access or not:
func isOpenAccessGranted() -> Bool{
if #available(iOS 10.0, *) {
let originalString = UIPasteboard.general.string
UIPasteboard.general.string = "Sour LeangChhean"
if UIPasteboard.general.hasStrings {
UIPasteboard.general.string = originalString ?? ""
return true
}else{
UIPasteboard.general.string = ""
return false
}
} else {
// Fallback on earlier versions
if UIPasteboard.general.isKind(of: UIPasteboard.self) {
return true
}else{
return false
}
}
}
Is there a way to know the cell carrier on an iPhone programmatically?
I am looking for the carrier name which the iPhone is connected to.
In iOS 4, the CoreTelephony framework is useable, here's a snippet to get the carrier name:
CTTelephonyNetworkInfo *netinfo = [[CTTelephonyNetworkInfo alloc] init];
CTCarrier *carrier = [netinfo subscriberCellularProvider];
NSLog(#"Carrier Name: %#", [carrier carrierName]);
[netinfo release];
Link against CoreTelephony and include in your headers:
#import <CoreTelephony/CTTelephonyNetworkInfo.h>
#import <CoreTelephony/CTCarrier.h>
Just to make a note here.. I tested this API on different SIMs and it seems that the name of the operator the iPhone is locked to is returned with [carrer carrierName]!!
I tested this on 2 iphones, one locked and the other not, and for the locked one, regardless of the SIM provider, it returns the name of the operator it is locked to everytime i run my test app. Note however that the MNC does change!
For swift users you can try this:
import CoreTelephony
static var carrierName:String? {
let networkInfo = CTTelephonyNetworkInfo()
let carrier = networkInfo.subscriberCellularProvider
return carrier?.carrierName
}
There is no public API for getting the carrier name. If you don't need to publish on the App Store you could look at using private api's.
VVCarrierParameters.h in the VisualVoiceMail package seems to have a carrierServiceName class method that might be what you need. Drop that header in your project and call [VVCarrierParameters carrierServiceName].
Note your app will most likely be rejected if you do this.
While developing Alpha, I encountered the same problem. The project itself was not limited to use only public API, so first I tried #Jason Harwig's solution. Because I could not get it to work, I thought of another option.
My solution uses private API to access the _serviceString ivar of the label (UIStatusBarServiceItemView) that is displayed in status bar.
It relies on status bar having a carrier value and only needs UIKit to work.
- (NSString *)carrierName
{
UIView* statusBar = [self statusBar];
UIView* statusBarForegroundView = nil;
for (UIView* view in statusBar.subviews)
{
if ([view isKindOfClass:NSClassFromString(#"UIStatusBarForegroundView")])
{
statusBarForegroundView = view;
break;
}
}
UIView* statusBarServiceItem = nil;
for (UIView* view in statusBarForegroundView.subviews)
{
if ([view isKindOfClass:NSClassFromString(#"UIStatusBarServiceItemView")])
{
statusBarServiceItem = view;
break;
}
}
if (statusBarServiceItem)
{
id value = [statusBarServiceItem valueForKey:#"_serviceString"];
if ([value isKindOfClass:[NSString class]])
{
return (NSString *)value;
}
}
return #"Unavailable";
}
- (UIView *)statusBar
{
NSString *statusBarString = [NSString stringWithFormat:#"%#ar", #"_statusB"];
return [[UIApplication sharedApplication] valueForKey:statusBarString];
}
I only tested the method with applications that have status bar visible. It returns the same string as it is displayed in status bar, so it works correctly even when roaming.
This method is not App Store safe.
Get carrier name from status bar in case if Core Telephony returns "Carrier"
func getCarrierName() -> String? {
var carrierName: String?
let typeName: (Any) -> String = { String(describing: type(of: $0)) }
let statusBar = UIApplication.shared.value(forKey: "_statusBar") as! UIView
for statusBarForegroundView in statusBar.subviews {
if typeName(statusBarForegroundView) == "UIStatusBarForegroundView" {
for statusBarItem in statusBarForegroundView.subviews {
if typeName(statusBarItem) == "UIStatusBarServiceItemView" {
carrierName = (statusBarItem.value(forKey: "_serviceString") as! String)
}
}
}
}
return carrierName
}
for Swift and ios 12.0 < do the following:
import CoreTelephony
static var carrierName:String? {
CTTelephonyNetworkInfo().serviceSubscriberCellularProviders?.first?.value.carrierName ?? ""
}
CTCarrier, carrierName and other info is deprecated as of iOS 16 with no replacement: https://developer.apple.com/documentation/coretelephony/ctcarrier.
https://developer.apple.com/iphone/prerelease/library/documentation/NetworkingInternet/Reference/CTCarrier/Reference/Reference.html#//apple_ref/doc/uid/TP40009596-CH1-DontLinkElementID_3
There is a such way however it's only available on iOS 4 so you won't be able to use it on previous versions. And this probably breaks your backward compatibility too.
When you print output of carrier?.description
This is what you see:
[\"0000000100000001\": CTCarrier (0x2803a1980) {\n\tCarrier name: [Vodafone]\n\tMobile Country Code: [214]\n\tMobile Network Code:[01]\n\tISO Country Code:[es]\n\tAllows VOIP? [YES]\n}\n]
Formatted (\n and \t):
[\"0000000100000001\": CTCarrier (0x2803a1980) {
Carrier name: [Vodafone]
Mobile Country Code: [214]
Mobile Network Code:[01]
ISO Country Code:[es]
Allows VOIP? [YES]
}
]
So get carrier name from status bar is a good option (at least for me)
I mean the answer of "codethemall" user.
More of an important comment. Just to add docs about the carrierName:
The carrier provides this string, formatting it for presentation to
the user. The value does not change if the user is roaming; it always
represents the provider with which the user has an account.
If you configure a device for a carrier and then remove the SIM card,
this property retains the name of the carrier. If you then install a
new SIM card, its carrier name replaces the previous value of this
property.
The value for this property is nil if the user never configured a
carrier for the device.