I am developing an application that I want to run on both iOS4 and iOS5 but for users of iOS5 I want to use an iOS5 feature as part of the interface (iOS4 users will get something less exciting). My question is what is the procedure for checking the availability of a particular OS on a device. My understanding is that I don't check the OS version but rather the availability of a particular class, can anyone help me out of the best way to do this ...
Isn't iOS 5 under NDA?
Anyway, to check if a feature exists then try this:
if (NSClassFromString(#"UIStepper")) {
//enter code here
} else {
//enter code here
}
Customise to your needs.
EDIT: iOS 5 is now released so I can now add "UIStepper" to my code.
iOS5 is under NDA so i wouldnt mention any new classes that may or may not exist. However the following code should do what you want. It's lifted from the docs.
if ([UINewClass class]) {
// Create an instance of the class and use it.
} else {
// Alternate code path to follow when the
// class is not available.
}
This uses Weak Linking and therefore requires that the new class (UINewClass) to be in the SDK you are using to compile. It is a relatively new feature introduced in iOS 4.2 and might not be supported by all the frameworks. A workaround is to use the older style (from the same link as above):
Class cls = NSClassFromString (#"UINewClass");
if (cls) {
// Create an instance of the class and use it.
} else {
// Alternate code path to follow when the
// class is not available.
}
Class stepperClass = NSClassFromString(#"UIStepper");
if (stepperClass) {
// class is available, use it
} else {
// class not available, don't use it or use something else
}
If you're trying to get the UIStepper to gracefully degrade in 4.x, you cannot only use
if( NSClassFromString(#"UIStepper") )
Instead, you must also check for a UIStepper-specific selector having a response. As Harry Wood suggested in the comment under Bo A, a good way to do it is:
if( NSClassFromString(#"UIStepper") && [theStepper respondsToSelector:#selector(setValue:)] )
This solved the issue of my app crashing under iOS 4.x.
Harry Wood helped me solve the issue I was seeing, and I would like him to get the credit.
Related
Inside of iOS SDK, lots of constants defined by Apple can be found looking something like this:
extern const CFStringRef kSomeReallyNiceConstant
__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_8_0);
If I check for presence of this constant standard way:
if (NULL == &kSomeReallyNiceConstant)
I am pretty much referencing it and in order for my code to compile properly, I need at least iOS SDK 8.0 or higher in this case.
When it comes to objects and methods, reflection approach works nicely with usage of NSClassFromString, respondsToSelector and performSelector.
Is there a chance to use some kind of reflection (access to string constant by name) in attempt to get it's value if it exists (or none if it doesn't)?
I know that I can use macros to check for iOS version and execute different code paths based on that information, but I don't want to use that approach.
I managed to do this with pointer:
#include <dlfcn.h>
// ...
int *pointer = dlsym(RTLD_SELF, "kSomeReallyNiceConstant");
if (pointer) {
NSLog(#"Thing exists!");
} else {
NSLog(#"Nope, doesn't exist!");
}
but I am not sure if this is something that would cause app rejection. Do you maybe know?
Regardless of this pointer approach, I'm curious to hear if there's any other way to achieve this?
Nothing better than suggested solution found on this topic.
#include <dlfcn.h>
// ...
int *pointer = dlsym(RTLD_SELF, "kSomeReallyNiceConstant");
if (pointer) {
NSLog(#"Thing exists!");
} else {
NSLog(#"Nope, doesn't exist!");
}
I am working on an app that is close to launch but uses the ABAddressBook framework. With the deprecation of ABAddressBook in iOS9, do I need to check the user's iOS version, and use ABAddressBook for pre iOS9 users, and CNContact for iOS9 users?
How is everyone else handling this? I haven't been in a situation like this before.
I have also been dealing-with and researching this issue, what I've opted to do is as you suggest; check the users iOS version doing something like the following:
NSString *version = [[UIDevice currentDevice] systemVersion];
BOOL isVersion8 = [version hasPrefix:#"8."];
BOOL isVersion7 = [version hasPrefix:#"7."];
//...
...continuing based on the versions you've decided to support for your app.
Then I do a check to either use the Addressbook framework for earlier than iOS 9, and Contacts framework for iOS 9 and beyond.
if(isVersion7 || isVersion8){
//Use AddressBook
}
else{
//Use Contacts
}
That's the best way I could think to deal with this deprecation business...
Deprecated doesn't mean removed. Just make linking to both frameworks as optional and start to design data workflow that can handle both frameworks. Also please mind that CNContact is new and full of bugs.
Once you think your app is refactored and iOS evolved to 9.1 give it a green light
How to know if system supports functionality
1) Check if the class exists
if(NSClassFromString(#"CNContact")) {
// Do something
}
For weakly linked classes, it is safe to message the class, directly. Notably, this works for frameworks that aren't explicitly linked as "Required". For missing classes, the expression evaluates to nil.
2)
#ifned NSFoundationVersionNumber_iOS_9
#def NSFoundationVersionNumber_iOS_9 NUMBER
#endif
if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_9) {
// Use address book
} else {
// Use contact framework
}
Run the app in simulator to find the NSFoundationVersionNumber constant
if #available(iOS 9, *)
{
// iOS 9 - CNContact
}
else
{
// iOS 8 - ABAddressBook
}
This is the right way to check.
I am developing an app using SDK 8.1, Apple LLVM 6.0 and Xcode 6.1.1. The deployment target is 6.0. I'm using NSOperationQueue and I want to use QoS whenever it's available.
The code I'm using is:
if ([self.operationQueue respondsToSelector:#selector(setQualityOfService:)]
&& (&NSOperationQualityOfServiceUserInitiated)) {
[self.operationQueue performSelector:#selector(setQualityOfService:) withObject: NSOperationQualityOfServiceUserInitiated];
} else {
//Other stuff not related to the scope of this question
}
The error I get is:
Use of undeclared identifier 'NSOperationQualityOfServiceUserInitiated'
I added the if (&NSOperationQualityOfServiceUserInitiated) part to check if this constant exists. This code worked with older versions of Xcode/Obj-C Compiler.
I am able to use selectors with performSelectorWithIdentifier but what about constants that do not have a defined value in the docs? The value of this constant is set by NSQualityOfServiceUserInitiated but there is no definition for this value that can be hardcoded.
How do I fix that?
There are several things wrong with the code.
NSOperationQualityOfServiceUserInitiated is a native type (NSInteger) so you can't use it the way that you are in either line.
The qualityOfService is a property of type NSQualityOfService. Your attempt to pass an argument to the qualityOfService method (getter method) makes no sense. If you are trying to set the quality of service, you need to call the setter but you can't use performSelector.
You want:
if ([self.operationQueue respondsToSelector:#selector(qualityOfService)]) {
self.operationQueue.qualityOfService = NSOperationQualityOfServiceUserInitiated;
} else {
//Other stuff not related to the scope of this question
}
This code will compile fine as long as your Base SDK is iOS 8.0 or later. The Deployment Target doesn't matter.
If you also want to build this code with Xcode 5 or earlier (a Base SDK of iOS 7 or earlier) then you need to wrap the code with the proper compiler directives to check for the Base SDK.
I am using ECSlidingViewController for "hamburger" menu. I am using SDK 7.0 but I changed deployment target to iOS 6.1 and now I am trying my app with older iOS then 7. The problem is with setEdgesForExtendedLayout. There is older ECSlidingViewController for older system versions. So my question is how can I change to use old version for iOS 6.1 and older and newer version for iOS 7.0 and newer. I include files from both ECSlidingViewController projects (not by cocoapods but if it is need then it's not problem to change it). I guess I need check for OS version and then change imports but I am not sure if it is enough and what's best name convention for both project. I guess they should be in different folders (like ECSlidingViewController and ECSlidingViewControllerOld) but class should be same name, is it right?
Edit: Example of code with edgesForExtendedLayout:
- (CGRect)underLeftViewCalculatedFrameForTopViewPosition:(ECSlidingViewControllerTopViewPosition)position {
CGRect frameFromDelegate = [self frameFromDelegateForViewController:self.underLeftViewController
topViewPosition:position];
if (!CGRectIsInfinite(frameFromDelegate)) return frameFromDelegate;
CGRect containerViewFrame = self.view.bounds;
if (!(self.underLeftViewController.edgesForExtendedLayout & UIRectEdgeTop)) {
CGFloat topLayoutGuideLength = [self.topLayoutGuide length];
containerViewFrame.origin.y = topLayoutGuideLength;
containerViewFrame.size.height -= topLayoutGuideLength;
}
if (!(self.underLeftViewController.edgesForExtendedLayout & UIRectEdgeBottom)) {
CGFloat bottomLayoutGuideLength = [self.bottomLayoutGuide length];
containerViewFrame.size.height -= bottomLayoutGuideLength;
}
if (!(self.underLeftViewController.edgesForExtendedLayout & UIRectEdgeRight)) {
containerViewFrame.size.width = self.anchorRightRevealAmount;
}
return containerViewFrame;
}
I am not a fan of including duplicate versions of libraries, as this creates a big problem with naming, and a lot of work to refactor all old classes to have some *-OLD suffix. Since you have access to the source, you can modify the newer version like so:
if(NSFoundationVersionNumber > NSFoundationVersionNumber_iOS_6_1)
{
[vc setEdgesForExtendedLayout:UIRectEdgeNone];
//Any other iOS7-specific code.
}
First, the easiest way to do it will be have the most recent version of component that support the lowest deployment target.
But if you really want to have different version for each iOS, I don't know better solution, than just rename all classes from, for example, older version (because there was only two classes) and manage creation of this controller programmatically, because there's no way to set different class for different iOS version in xib's or storyboard's. You need to wrap each and every call of this component with iOS version check ( How to check iOS version? ).
Imports and variables you can leave for each version of component without check.
The trick you mentioned failed in this case, because it works good for different architecture, because binaries for different architectures will be included in final app and that's named fat binary, but there's the same architecture for iOS6 and iOS7 ( only one new in iOS7 - arm64). So you can't wrap only includes with preprocessor macros and get fat binary with different code for each iOS version.
I hope you've understood something from my explanation.
It seems that the only thing preventing you from using the new version with iOS 6 is the lack of topLayoutGuide and bottomLayoutGuide, which if available would just return lengths of zero anyway.
Why not "backport" those methods in your controller?
- (id<UILayoutSupport>)topLayoutGuide
{
CustomLayoutGuide * guide = [CustomLayoutGuide new];
guide.length = SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(#"7.0") ? super.topLayoutGuide.length : 0.0;
return guide;
}
And the helper class
#interface CustomLayoutGuide : NSObject <UILayoutSupport>
#property(nonatomic) CGFloat length;
#end
#implementation CustomLayoutGuide
#end
IMHO the easiest thing to do (and probably the best though) is to take the current ECSlidingViewController and integrate part of the old when edgesForExtendedLayout is not available (e.g. if ([self respondsToSelector:#selector(edgesForExtendedLayout)]) { /*...*/} ). This should be pretty easy and fast to do since the old ECSlidingViewController just consists of two files and these files are also present in the new ECSlidingViewController.
You could make your life even easier when doing this by using a diff tool to be sure of what you should add.
I'm looking to make my app compatible with older versions of iPhone OS. I did see weak linking mentioned as an option. Can I use OS version detection code to avoid code blocks that the OS can't handle? (Say iAD?)
if(OS >= 4.0){
//set up iADs using "NDA code"...
}
If yes, what goes in place of if(OS >= 4.0)?
You should be weak linking against the new frameworks. Alongside that you should be checking the availability of new APIs using methods like NSClassFromString, respondsToSelector, instancesRespondToSelector etc.
Eg. Weak linking against MessageUI.framework (an old example, but still relevant)
First check if the MFMailComposerController class exists:
Class mailComposerClass = NSClassFromString(#"MFMailComposerController");
if (mailComposerClass != nil)
{
// class exists, you can use it
}
else
{
// class doesn't exist, work around for older OS
}
If you need to use new constants, types or functions, you can do something like:
if (&UIApplicationWillEnterBackgroundNotification != nil)
{
// go ahead and use it
}
If you need to know if you can use anew methods on an already existing class, you can do:
if ([existingInstance respondsToSelector:#selector(someSelector)])
{
// method exists
}
And so on. Hope this helps.