respondsToSelector but the selector is unrecognized - ios

I'm following apple's example code to the letter for how to implement receipt validation under iOS 7, and it works, except when I run the following code (taken basically verbatim from their sample) under iOS 6
NSBundle *bundle =[NSBundle mainBundle];
if ([bundle respondsToSelector:#selector(appStoreReceiptURL)]) { // can do local device receipt validation
NSURL *receiptURL = [bundle performSelector:#selector(appStoreReceiptURL)];
}
It returns true to the responds to selector, and therefore tries to perform the selector at which point it crashes because the selector doesn't exist... Why am I getting a positive response to a selector that doesn't exist?

The documentation for appStoreReceiptURL explains that this method existed as a private method before iOS 7, and that its implementation prior to iOS 7 calls doesNotRecognizeSelector:. Therefore you cannot use respondsToSelector: to check whether it's ok to call the method.
Instead, you need to check the system version:
NSString *version = [UIDevice currentDevice].systemVersion;
if ([version compare:#"7.0" options:NSNumericSearch] != NSOrderedAscending) {
// safe to use appStoreReceiptURL
} else {
// not safe to use appStoreReceiptURL
}

I also got bitten by the bad sample code given at the WWDC session. It looks like Apple has updated their documentation with new reccomended sample code:
if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_6_1) {
// Load resources for iOS 6.1 or earlier
} else {
// Load resources for iOS 7 or later
}
Based on this sample, you could write it in a single branch like so if you prefer, and check afterwards if the object is nil:
NSURL* url = nil;
if (floor(NSFoundationVersionNumber) > NSFoundationVersionNumber_iOS_6_1) {
//iOS 7 or later, safe to use appStoreReceiptURL
url = [[NSBundle mainBundle] performSelector:#selector(appStoreReceiptURL)];
}

I saw that in the WWDC 2013 talk (e.g., “Using Receipts to Protect Your Digital Sales”) too. And the conflicting statement in the appStoreReceiptURL docs. It seems that the WWDC 2013 code example for appStoreReceiptURL was untested.

Related

Getting existing app with fingerprint authentication to work with iPhone X FaceId

I have an app already developed and is live on the store. Its working well and has fingerprint authentication implemented. Now that Apple has made it mandatory to provide iPhone X support, I will be releasing one more update of the app.
But, i wish to understand what will happen if install the already submitted version of the app on iPhone X..?
I have read that -
App will provide a compatibility alert saying This app was designed to use Touch ID and may not fully support FaceID, But it goes away if we insert a key NSFaceIDUsageDescription in the plist.
I have also read that the app will crash if build using iOS 11 SDk (without the key NSFaceIDUsageDescription in the plist).
Here is my question - what if the app is build using iOS 8 and installed in iOS 11, will it still crash ? if not, how will be the exact behaviour..? I have tested in the simulator and the app did not crash but i do not have iPhone X available at hands and cannot crosscheck the same on the actual device.
NOTE - This crash is observed on the device more and not on simulator.
How important it is to have the word FaceId in the app running on iPhone X. What if I use a general terms like "Biometric authentication" in place of touchId & FaceId. Will the app still be fine and won't end up rejected ?
Any help or leads will be truly appreciated!
You're right in that you need the NSFaceIDUsageDescription key in your plist to properly support Face ID.
If you built the app using Xcode 9 against the iOS 11 SDK, even if you set the target to iOS 8, you need to include that key to support iPhone X.
That said, I don't see how this might be crashing. The same code that you use for Touch ID also supports Face ID.
I use this to handle faceID or touchID
if (#available(iOS 11.0, *)) {
if ([myContext canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&authError]) {
if (myContext.biometryType == LABiometryTypeTouchID) {
SwitchItem *touchIdItem = [[SwitchItem alloc] init];
touchIdItem.name = NSLocalizedString(#"PROFILE_SETTINGS_TOUCHID", nil);
touchIdItem.active = [[[Global instance] objectInKey:KEY_TOUCHID_ENABLED] boolValue];
[self.dataSource addObject:touchIdItem];
} else if (myContext.biometryType == LABiometryTypeFaceID) {
SwitchItem *faceIdItem = [[SwitchItem alloc] init];
faceIdItem.name = NSLocalizedString(#"PROFILE_SETTINGS_FACEID", nil);
faceIdItem.active = [[[Global instance] objectInKey:KEY_FACEID_ENABLED] boolValue];
[self.dataSource addObject:faceIdItem];
}
}
} else {
if ([myContext canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&authError]) {
SwitchItem *touchIdItem = [[SwitchItem alloc] init];
touchIdItem.name = NSLocalizedString(#"PROFILE_SETTINGS_TOUCHID", nil);
touchIdItem.active = [[[Global instance] objectInKey:KEY_TOUCHID_ENABLED] boolValue];
[self.dataSource addObject:touchIdItem];
}
}
You have to evaluate iOS version and after that yo can suppose if the gadget have a touchID or faceID

How to detect iOS version in Objective C?

I'm using Firebase 3.7.x to store my database. Firebase 3.7.x is support iOS 7.0 or higher but my project supports from iOS 6.0. So I want to detect iOS version in device to call #import Firebase. Something like that:
if IOS_7_OR_HIGHER
#import Firebase
else
//do nothing
if IOS_7_OR_HIGHER
- (void)dosomething{}
else
- (void)donothing {}
I know about if #available in swift. Is there any code like if #available in Objective C? Or is there any way to import Firebase for iOS 7 or higher and disable disable for iOS6?
Thanks.
You can get device system version by using
-(NSString*)getDeviceVersion{
return [[UIDevice currentDevice] systemVersion];
}
it will return you device version as string e.g. #"4.0" .
Hope it help you.
Try below code:
NSArray *osVersion = [[UIDevice currentDevice].systemVersion componentsSeparatedByString:#"."];
if ([[osVersion objectAtIndex:0] intValue] >= 7) {
// iOS-7 or greater
} else if ([[osVersion objectAtIndex:0] intValue] == 6) {
// iOS-6 code
} else if ([[osVersion objectAtIndex:0] intValue] > 2) {
// iOS-3,4,5 code
} else {
// iOS-1,2... code
}
To answer your question you can do it like this:
#ifdef __IPHONE_6_0
//Do something patchy!
#else
#import Firebase
#endif
Humble suggestion: You can consider upgrading your app.
A recent iOS version stats counter from Apple showing that there are only 5% devices which are still having iOS 8, 7 or <= 6. Means, you should drop out support for all those versions or you should start supporting iOS9 onwards.
By doing this you will get all the latest iOS features and you will never have to make this kind of patch in future.
Source: https://developer.apple.com/support/app-store/

Shared Core Data - What am I missing?

I have an app with a Today extension. Using App Groups, I am able to have a single repository for both the app and the extension. However, I must be missing something as the solution only partially works. If I am in the App I can add a record and will see that same record in the widget. However, if a change the value of a column, for example, setting a boolean from true to false. The app won't see the change if it were made in the extension and vice versa. I am saving the change to Core Data:
_record?.managedObjectContext?.save()
Using DB Browser, I am able to verify that the change was made; is in the DB. Clearly, I am missing something. Any ideas would be appreciated.
Make sure you're using the same db in both sides
- (void)viewDidLoad {
[super viewDidLoad];
if ([self.extensionContext respondsToSelector:#selector(setWidgetLargestAvailableDisplayMode:)]) { // iOS 10+
[self.extensionContext setWidgetLargestAvailableDisplayMode:NCWidgetDisplayModeExpanded];
} else {
self.preferredContentSize = CGSizeMake(0, 110.0); // iOS 10-
}
[MagicalRecord setupCoreDataStackWithAutoMigratingSqliteStoreAtURL:[MagicalRecord urlAppGroupStore]];
}
As you can see i'm using magical record but I'm specifying to use the share sqlite file
NSString *const kAppGroupIdentifier = #"group.com.carlosduclos.myapp";
+ (NSURL *)urlAppGroupStore {
NSURL *groupURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:kAppGroupIdentifier];
return [groupURL URLByAppendingPathComponent:#"mydb.sqlite"];
}

iOS Extension - Fatal Exception: com.firebase.core Default app has already been configured

iOS Extension - Fatal Exception: com.firebase.core Default app has already been configured.
I run the Fir.configure() in the viewDidLoad() method and some events pass and get through to Firebase.
Can someone help me figure this out.. Google is not friendly enough.
PS: Yes I created a second .plist, and a second app in Firebase Console.
PPS: Yes I selected the correct target for each GoogleServices plist
I'm seeking the solution..
I don't know if you're still looking for a solution but I had the same issue, using firebase with extension.
I ended up doing this :
if(FIRApp.defaultApp() == nil){
FIRApp.configure()
}
This will check if the app is already configured so that there is no crash.
I had the same issue and I resolved it by making changes in two places in my application (check your syntax for swift version):
In AppDelegate.swift override a following method:
override init()
{
FirebaseApp.configure()
}
In your ViewController.swift's viewDidLoad() method, write this code:
if FirebaseApp.app() == nil {
FirebaseApp.configure()
}
Use this instead for latest firebase_ml_vision: ^0.9.3+8
if(FirebaseApp.app() == nil){
FirebaseApp.configure()
}
The answer is in the exception. You're configuring the app twice. You've got a few options to fix this:
1) Configure your app once in your app delegate.
2) Match a configure with an unconfigure. There is a method on FIRApp that allows you to unconfigure it (the actual name escapes me).
I also got this issue in a today extension code too. I solved if with an extra check if your Firebase instance is already configured:
// define in your constants or somewhere
NSString* const UNIQUE_FIREBASE_INSTANCE_IDENTIFIER_WIDGET = #"Widget_FirebaseAppInstance";
if([FIRApp appNamed:UNIQUE_FIREBASE_INSTANCE_IDENTIFIER_WIDGET] == nil) // there should be only one app instance!
{
NSString *resourceName = [[[NSBundle mainBundle] infoDictionary] objectForKey:#"FirebaseResource"];
NSString *filePath = [[NSBundle mainBundle] pathForResource:resourceName ofType:#"plist"];
FIROptions *options = [[FIROptions alloc] initWithContentsOfFile:filePath];
[FIRApp configureWithName:UNIQUE_FIREBASE_INSTANCE_IDENTIFIER_WIDGET options:options];
}
In your main app you should also use a different unique name for it. So you can have multiple instances but every instance is configured only once.

Use NSUserActivity and CoreSpotlight but still set iOS8 as Deployment Target

Is it possible to use the new features of iOS9 such as NSUserActivity and CoreSpotlight, but still set my Development Target to 8.2 so that users with iOS8 can still use the app?
I assume I would just need to do a iOS version number check or use respondsToSelector:.
Is this correct?
Yes, I do it in one of my apps (actually have a deployment target of iOS 7). It's trivial to do. Just make sure the CSSearchableIndex class exists, make the CoreSpotlight framework optional, and write your code properly to prevent the newer APIs from being run on devices with earlier versions of iOS.
You can even guard the code so it compiles under Xcode 6 if you had some reason to do so.
Example:
// Ensure it only compiles with the Base SDK of iOS 9 or later
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 90000
// Make sure the class is available and the device supports CoreSpotlight
if ([CSSearchableIndex class] && [CSSearchableIndex isIndexingAvailable]) {
dispatch_async(_someBGQueue, ^{
NSString *someName = #"Some Name";
CSSearchableIndex *index = [[CSSearchableIndex alloc] initWithName:someName];
// rest of needed code to index with Core Spotlight
});
}
#endif
In your app delegate:
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 90000
- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void(^)(NSArray *restorableObjects))restorationHandler {
if ([[userActivity activityType] isEqualToString:CSSearchableItemActionType]) {
// This activity represents an item indexed using Core Spotlight, so restore the context related to the unique identifier.
// The unique identifier of the Core Spotlight item is set in the activity’s userInfo for the key CSSearchableItemActivityIdentifier.
NSString *uniqueIdentifier = [userActivity.userInfo objectForKey:CSSearchableItemActivityIdentifier];
if (uniqueIdentifier) {
// process the identifier as needed
}
}
return NO;
}
#endif

Resources