Detect BOOL availability to support multiple iOS versions? - ios

I am trying to find out how one can detect if an external BOOL is available so that I can support iOS 7 and 8. New in iOS 8 is a BOOL you can use to find out if Reduce Transparency is enabled, and I want to implement that check in an if statement, but this will crash on iOS 7 without first checking if the extern BOOL is available. I was surprised I could not find the answer from my web searches.
Here is the BOOl definition:
UIKIT_EXTERN BOOL UIAccessibilityIsReduceTransparencyEnabled() NS_AVAILABLE_IOS(8_0);
And the location I'm using it:
if (UIAccessibilityIsReduceTransparencyEnabled()) {
NSLog(#"transparency is disabled");
}

Please read the SDK Compatibility Guide.
What you need to do is check if the function UIAccessibilityIsReduceTransparencyEnabled exists:
if (UIAccessibilityIsReduceTransparencyEnabled != NULL) {
// function exists, use it
if (UIAccessibilityIsReduceTransparencyEnabled()) {
NSLog(#"transparency is disabled");
}
} else {
// function doesn't exist, do something else
}

Related

How to check if iOS application uses 'StoreKit.framework'?

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

Knowing programmatically if cell data is disabled for the app for iOS [duplicate]

I have an iOS app that makes some small network requests on app launch (resource updates, etc). If the user turns off cellular access for the app in iOS Settings, they get a prompt from iOS about network usage every time they launch. Is there a way to know programmatically that cellular data for this app has been disabled, so that I can disable the requests at startup?
So I found this on the apple dev forums from an Apple engineer (https://devforums.apple.com/message/1059332#1059332).
Another developer wrote in to DTS and thus I had a chance to
investigate this in depth. Alas, the news is much as I expected:
there is no supported way to detect that your app is in this state.
Nor is there a way to make a "no user interaction" network connection,
that is, request that the connection fail rather than present UI like
this. If these limitations are causing problems for your app, I
encourage you to file a bug describing your specific requirements.
https://developer.apple.com/bug-reporting/
So it looks like it is not possible to detect if cellular data for your app has been turned off.
Edit
I filed a radar for this requesting that it be added. I just got this notification in my radar
We believe this issue has been addressed in the latest iOS 9 beta.
I looked through the API diffs, but so far I can't find the new API.
As of iOS9, the capability to check the setting to enable/disable use of cellular data for your app (Settings/Cellular/AppName) is available using Apple's CTCellularData class. The following code will set cellularDataRestrictedState when it is run initially and then set it and log whenever it changes:
import CoreTelephony
var cellularDataRestrictedState = CTCellularDataRestrictedState.restrictedStateUnknown
let cellState = CTCellularData.init()
cellState.cellularDataRestrictionDidUpdateNotifier = { (dataRestrictedState) in
if cellularDataRestrictedState != .restrictedStateUnknown { // State has changed - log to console
print("cellularDataRestrictedState: " + "\(dataRestrictedState == .restrictedStateUnknown ? "unknown" : dataRestrictedState == .restricted ? "restricted" : "not restricted")")
}
cellularDataRestrictedState = dataRestrictedState
}
Unfortunately (as of iOS11) this seems to check only the state of the app's switch - if your app's switch is set to enabled and the user switches the Cellular Data master switch to disabled, this API will return the app's state as being "not restricted".
Just wanted to add an Objective C version of the above Swift code for future travellers.
- (void)monitorCanUseCellularData {
if (GCIsiOS9) {
CTCellularData *cellularData = [[CTCellularData alloc] init];
NSLog(#"%ld", cellularData.restrictedState);
// 0, kCTCellularDataRestrictedStateUnknown
[cellularData setCellularDataRestrictionDidUpdateNotifier:^(CTCellularDataRestrictedState state) {
NSLog(#"%ld", state);
self.canUseCellularData = cellularData.restrictedState ==2?true:false;
}];
}
}
I have found that the CTCellularData class needs some time to get to the correct value. In my implementation I call the didUpdateNotifier very early after appDidFinishLaunching. By the time my networking call are returning with errors I definitely have a correct value for the restricted state.
class CellularRestriction: NSObject {
private static var cellularData = CTCellularData()
private static var currentState = CTCellularDataRestrictedState.restrictedStateUnknown
static var isRestricted: Bool {
currentState = cellularData.restrictedState
return currentState == .restricted
}
static func prepare() {
if currentState == .restrictedStateUnknown {
cellularData.cellularDataRestrictionDidUpdateNotifier = { state in
currentState = cellularData.restrictedState // This value may be inconsistent, however the next read of isRestricted should be correct.
}
}
}
}
You can detect if cellular data disabled using NWPathMonitor class. (https://developer.apple.com/documentation/network/nwpathmonitor)
let cellMonitor = NWPathMonitor(requiredInterfaceType: .cellular)
cellMonitor.pathUpdateHandler = { path in
self.isCellConnected = path.status == .satisfied
}
Adding to dirkgroten's answer, you can use the Apple Reachability class, found here:
https://developer.apple.com/Library/ios/samplecode/Reachability/Introduction/Intro.html
It uses SCNetworkReachability, and is very straight forward to use, it will detect connectivity via Cell and WiFi as you will need to check both at start up.
There are lots of frameworks out there that will give you the status of your network connectivity, and of course you can roll your own. I've found AFNetworking to be one of the best. It has a singleton class called AFNetworkReachabilityManager that abstracts some of the complexities for you. Specifically you'll want to look at the two boolean properties:
reachableViaWWAN
reachableViaWiFi
There is also a reachability changed status block that you can set:
– setReachabilityStatusChangeBlock:
AFNetworking Github
AFNetworkReachabilityManager

Check if a function is available in Swift?

I would like to detect if the user has enabled Reduce Transparency. It's simple you just call the func UIAccessibilityIsReduceMotionEnabled() and it returns a Bool. But my app targets iOS 7 and 8 and this function isn't available on iOS 7.
In Objective-C, this is how I checked to see if that function exists:
if (UIAccessibilityIsReduceMotionEnabled != NULL) { }
In Swift, I can't figure out how to check if it exists or not. According to this answer, you can simply use optional chaining and if it's nil then it doesn't exist, but that is restricted to Obj-C protocols apparently. Xcode 6.1 doesn't like this:
let reduceMotionDetectionIsAvailable = UIAccessibilityIsReduceMotionEnabled?()
It wants you to remove the ?. And of course if you do so it will crash on iOS 7 because that function doesn't exist.
What is the proper way to check if these types of functions exist?
A proper check for availability has been added in Swift 2. This is recommended over other options mentioned here.
var shouldApplyMotionEffects = true
if #available(iOS 8.0, *) {
shouldApplyMotionEffects = !UIAccessibilityIsReduceMotionEnabled()
}
If you're okay with being a little bit cheeky, you can always open the UIKit binary using the library loader and see if it can resolve the symbol:
let uikitbundle = NSBundle(forClass: UIView.self)
let uikit = dlopen(uikitbundle.executablePath!, RTLD_LAZY)
let handle = dlsym(uikit, "UIAccessibilityIsReduceMotionEnabled")
if handle == nil {
println("Not available!")
} else {
println("Available!")
}
The dlopen and dlsym calls can be kinda expensive though so I would recommend keeping the dlopen handle open for the life of the application and storing somewhere the result of trying to dlsym. If you don't, make sure you dlclose it.
As far as I know this is AppStore safe, since UIAccessibilityIsReduceMotionEnabled is a public API.
You could check to see if you're running in iOS 8 or higher --
var reduceMotionEnabled = false
if NSProcessInfo().isOperatingSystemAtLeastVersion(NSOperatingSystemVersion(majorVersion: 8, minorVersion: 0, patchVersion: 0)) {
reduceMotionEnabled = UIAccessibilityIsReduceMotionEnabled()
}
I don't think there's another way to tell. So in theory, if you were able to check, trying to access the function name without the () would give you nil in iOS 7 and the () -> Bool function in iOS 8. However, in order for that to happen, UIAccessibilityIsReduceMotionEnabled would need to be defined as (() -> Bool)?, which it isn't. Testing it out yields a function instance in both versions of iOS that crashes if called in iOS 7:
let reduceMotionDetectionIsAvailable = UIAccessibilityIsReduceMotionEnabled
// reduceMotionDetectionIsAvailable is now a () -> Bool
reduceMotionDetectionIsAvailable()
// crashes in iOS7, fine in iOS8
The only way I can see to do it without testing the version is simply to define your own C function to check in your bridging header file, and call that:
// ObjC
static inline BOOL reduceMotionDetectionIsAvailable() {
return (UIAccessibilityIsReduceMotionEnabled != NULL);
}
// Swift
var reduceMotionEnabled = false
if reduceMotionDetectionIsAvailable() {
reduceMotionEnabled = UIAccessibilityIsReduceMotionEnabled()
}
From the Apple Developer docs (Using Swift with Cocoa and Objective-C (Swift 3) > Interoperability > Adopting Cocoa Design Patterns > API Availability):
Swift code can use the availability of APIs as a condition at
run-time. Availability checks can be used in place of a condition in a
control flow statement, such as an if, guard, or while
statement.
Taking the previous example, you can check availability in an if
statement to call requestWhenInUseAuthorization() only if the method
is available at runtime:
let locationManager = CLLocationManager()
if #available(iOS 8.0, macOS 10.10, *) {
locationManager.requestWhenInUseAuthorization()
}
Alternatively, you can check availability in a guard statement,
which exits out of scope unless the current target satisfies the
specified requirements. This approach simplifies the logic of handling
different platform capabilities.
let locationManager = CLLocationManager()
guard #available(iOS 8.0, macOS 10.10, *) else { return }
locationManager.requestWhenInUseAuthorization()
Each platform argument consists of one of platform names listed below,
followed by corresponding version number. The last argument is an
asterisk (*), which is used to handle potential future platforms.
Platform Names:
iOS
iOSApplicationExtension
macOS
macOSApplicationExtension
watchOS
watchOSApplicationExtension
tvOS
tvOSApplicationExtension

Check for class existence in Swift

I want to use NSURLQueryItem in my Swift iOS app. However, that class is only available since iOS 8, but my app should also run on iOS 7. How would I check for class existence in Swift?
In Objective-C you would do something like:
if ([NSURLQueryItem class]) {
// Use NSURLQueryItem class
} else {
// NSURLQueryItem is not available
}
Related to this question is: How do you check for method or property existence of an existing class?
There is a nice section in https://developer.apple.com/library/ios/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/AdvancedAppTricks/AdvancedAppTricks.html#//apple_ref/doc/uid/TP40007072-CH7-SW4 called Supporting Multiple Versions of iOS, which explains different techniques for Objective-C. How can these be translated to Swift?
Swift 2.0 provides us with a simple and natural way to do this.It is called API Availability Checking.Because NSURLQueryItem class is only available since iOS8.0,you can do in this style to check it at runtime.
if #available(iOS 8.0, *) {
// NSURLQueryItem is available
} else {
// Fallback on earlier versions
}
Simplest way I know of
if NSClassFromString("NSURLQueryItem") != nil {
println("NSURLQueryItem exists")
}else{
println("NSURLQueryItem does not exists")
}
Try this:
if objc_getClass("NSURLQueryItem") != nil {
// iOS 8
} else {
// iOS 7
}
I've also done it like this too:
if let theClass: AnyClass = NSClassFromString("NSURLQueryItem") {
// iOS 8
} else {
// iOS 7
}
Or, you can also check system version like so, but this isn't the best practice for iOS dev - really you should check if a feature exists. But I've used this for a few iOS 7 hacks... pragmatism over purity.
switch UIDevice.currentDevice().systemVersion.compare("8.0.0", options: NSStringCompareOptions.NumericSearch) {
case .OrderedSame, .OrderedDescending:
iOS7 = false
case .OrderedAscending:
iOS7 = true
}

Check function availability in Core Foundation

I want to use a Core Foundation method that is only available on the latest iOS SDK, and fallback to a different one for previous SDKs. Is there something similar to Cocoa's way of doing it for NSObject?:
- (BOOL)respondsToSelector:(SEL)aSelector
A bit more of context, let's say ABAddressBookCreate() would only be available in iOS5, and my app is targeting iOS 4.3+. I would want to check if ABAddressBookCreate() is available at runtime and perform different actions depending on that.
Try this (doesn't need weak linking):
#include <dlfcn.h>
void *handle = dlopen("/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation", RTLD_NOW);
void *createFunc = dlsym(handle, "ABAddressBookCreate");
if (createFunc != NULL)
{
// available
ABAddressBookRef (*_ABAddressBookCreate)(void) = createFunc;
// then use this function pointer as normally
}
If you can provide weak linking:
if (ABAddressBookCreate != NULL)
{
// available
}
I've never tried it, but maybe this is helpful:
https://developer.apple.com/library/mac/#documentation/DeveloperTools/Conceptual/cross_development/Using/using.html
Check listing 3-2.

Resources