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.
Related
I'm implementing the new WCSessionDelegate methods to support multiple device pairing.
- (void)session:(WCSession *)session activationDidCompleteWithState:(WCSessionActivationState)activationState error:(nullable NSError *)error;
- (void)sessionDidBecomeInactive:(WCSession *)session;
- (void)sessionDidDeactivate:(WCSession *)session;
I'm a bit unsure about how these methods will work with older versions of iOS and watchOS (the Simulator is proving very unhelpful).
My assumption is that these methods will replace the behaviour controlled through the method below, and I can exclude it?
- (void)sessionWatchStateDidChange:(WCSession *)session;
Has anyone had experience with supporting combinations of older iOS and watchOS devices with these new methods?
The new session activation methods don't replace the state change method. You will continue to receive state change notifications for the active watch since some property changes may not be related to the watch being switched.
For example, the user may install or delete the companion watch app, or enable or disable the complication on the currently active watch.
Supporting older versions of iOS:
The delegate methods themselves won't get called on older versions of the OS. You merely need to ensure you don't access any properties or call any methods which would only be available on newer versions of the OS.
You should use #if available to dynamically check the OS version that your app is running on (since activationState is only available since 9.3).
Here's an example demonstrating how you could support multiple versions of iOS:
private func isValidSession() -> Bool {
if #available(iOS 9.3, *) {
guard let session = session where session.activationState == .Activated && session.paired && session.watchAppInstalled else {
return false
}
} else {
// Fallback on earlier versions
guard let session = session where session.paired && session.watchAppInstalled else {
return false
}
}
return true
}
Sample code:
Apple has also provided QuickSwitch sample code which you may find helpful in supporting or experimenting with watch switching.
I'm creating an app that works with CloudKit framework (iOS 8-only) but still want to keep compatibility with iOS 7.1, with no CloudKit functionality, of course. Apple documentation recommends checking for optional classes like this:
if ([CKRecordID class]) {
// iOS 8 code
} else {
// iOS 7 code
}
This works. On the other hand, if I write
#interface Foo : NSObject
#property (nonatomic) CKRecordID *recordID;
#end
anywhere in the code, the app will crash on iOS 7 when loading the Foo class. How can I define properties with those optional classes?
You could use the forward declaration
#class CKRecordID;
but you will need runtime checks for the iOS version, such as
[[NSProcessInfo processInfo] operatingSystemVersion]
Other solutions for detecting the iOS version are shown here or here.
But how about two different builds for different iOS versions?
You can make your property recordId of type id or NSObject.
And when you need to access this property (after checking that your runtime is iOS8+), you cast it to CKRecordID class.
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 have an app that must work for iOS versions >= 5.1.
Apple docs say that "The AudioSession API has been completely deprecated in iOS 7.0". And one should use AVAudioSession class instead.
But the method that I need (- (BOOL)setCategory:(NSString *)category withOptions:(AVAudioSessionCategoryOptions)options error:(NSError **)outError) is only available starting from iOS 6.0.
So it seems that I have to continue usage of deprecated AudioSessionSetProperty to support iOS 5.1.
What is a correct way to handle such situations (my question is general, not only about this particular problem with audio)? Should I write something like
if ([[[UIDevice currentDevice] systemVersion] compare:#"6.0" options:NSNumericSearch] == NSOrderedAscending)
{
// use deprecated methods (AudioSessionSetProperty)
}
else
{
// use brand-new methods (AVAudioSession)
}
or maybe it's ok to just use AudioSessionSetProperty until it stops to compile?
Deprecated methods doesn't stops you from using it, but yes if its deprecated from your current selected iOS version then it'll give you warning. An example, if your app still supporting iOS < 6.0 and you're need to change alignment of UILabel then you can do it like this,
if ([[[UIDevice currentDevice] systemVersion] compare:#"6.0" options:NSNumericSearch] == NSOrderedDescending)
{
lbl.textAlignment = UITextAlignmentCenter;
//for iOS < 6.0, warning when deployment target is iOS 5.0
}
else
{
lbl.textAlignment = NSTextAlignmentCenter; //for iOS >= 6.0
}
OR
lbl.textAlignment = UITextAlignmentCenter; //with warning in iOS 6.0
OR
lbl.textAlignment = NSTextAlignmentCenter; //will crash in iOS 5.0
PS. better to support iOS 7.0 and higher (not even iOS 6.0 as Apple wouldn't allow apps with iOS6.0 from coming Feb as commented by #ahwulf.), as latest iOS 8 was released with lots of good features and most of the people updated their phone with this. This will help you in two cases, you'll not need to check for iOS version compatibility and your code looks nice without warnings :) but still its all depends your needs.
Update:
OR
as #rmaddy suggested one should check for existence of method using respondsToSelector:
UILabel *label;
if([label respondsToSelector:#selector(textAlignment)]) {
label.textAlignment = NSTextAlignmentCenter;
}
In above example, we're checking (but in real we're already know this) whether UILabel has textAlignment property or not.
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.