Check if device supports blur - ios

My app uses UIBlurEffect, however older devices (specifically iPads 2 and 3, that support iOS 8) don't have blur support.
I'd like to check if the user's device has blur support or not. How do I do it?

UIDevice has an internal method [UIDevice _graphicsQuality] that seems promising, but of course your app will be rejected by Apple. Let's create our own method:
First of all, we need to know the exact device type we're working on:
#import <sys/utsname.h>
NSString* deviceName()
{
struct utsname systemInfo;
uname(&systemInfo);
return [NSString stringWithCString:systemInfo.machine
encoding:NSUTF8StringEncoding];
}
This should return iPad2,1 for iPad 2, for example. Here's an updated list of iDevice models: https://theiphonewiki.com/wiki/Models
So, let's classify our device models in two groups: those that have poor graphics quality (and thus don't support blur), and those with great graphics quality. According to my investigation, these are the devices that Apple considers with "poor" graphics (these may change in the future):
iPad iPad1,1 iPhone1,1 iPhone1,2 iPhone2,1 iPhone3,1 iPhone3,2
iPhone3,3 iPod1,1 iPod2,1 iPod2,2 iPod3,1 iPod4,1 iPad2,1 iPad2,2
iPad2,3 iPad2,4 iPad3,1 iPad3,2 iPad3,3
So we write the following code:
NSSet *graphicsQuality = [NSSet setWithObjects:#"iPad",
#"iPad1,1",
#"iPhone1,1",
#"iPhone1,2",
#"iPhone2,1",
#"iPhone3,1",
#"iPhone3,2",
#"iPhone3,3",
#"iPod1,1",
#"iPod2,1",
#"iPod2,2",
#"iPod3,1",
#"iPod4,1",
#"iPad2,1",
#"iPad2,2",
#"iPad2,3",
#"iPad2,4",
#"iPad3,1",
#"iPad3,2",
#"iPad3,3",
nil];
if ([graphicsQuality containsObject:deviceName()]) {
// Device with poor graphics, blur not supported
} else {
// Blur supported
}
Be careful because even though the device may support blur, the user may have disabled advanced visual effects from Settings, Accessibility.
Alternative method
https://gist.github.com/conradev/8655650

Here is a Swift version of Daniel Martin's answer. Some of the code was adapted from here:
func isBlurSupported() -> Bool {
var supported = Set<String>()
supported.insert("iPad")
supported.insert("iPad1,1")
supported.insert("iPhone1,1")
supported.insert("iPhone1,2")
supported.insert("iPhone2,1")
supported.insert("iPhone3,1")
supported.insert("iPhone3,2")
supported.insert("iPhone3,3")
supported.insert("iPod1,1")
supported.insert("iPod2,1")
supported.insert("iPod2,2")
supported.insert("iPod3,1")
supported.insert("iPod4,1")
supported.insert("iPad2,1")
supported.insert("iPad2,2")
supported.insert("iPad2,3")
supported.insert("iPad2,4")
supported.insert("iPad3,1")
supported.insert("iPad3,2")
supported.insert("iPad3,3")
return !supported.contains(hardwareString())
}
func hardwareString() -> String {
var name: [Int32] = [CTL_HW, HW_MACHINE]
var size: Int = 2
sysctl(&name, 2, nil, &size, &name, 0)
var hw_machine = [CChar](count: Int(size), repeatedValue: 0)
sysctl(&name, 2, &hw_machine, &size, &name, 0)
let hardware: String = String.fromCString(hw_machine)!
return hardware
}

Many thanks do Daniel Martin for the great answer.
I have made a UIDevice category that combines the following checks for availability of the blur effect:
iOS version
Device type (thanks to Daniel Martin)
Private UIDevice method _graphicsQuality when building for the simulator.
(This is done since the device type check does not work in the simulator - will be compiled out when building for arm architectures)
'Reduce Transparency' accessibility setting
I've made a gist containing the category:
https://gist.github.com/mortenbekditlevsen/5a0ee16b73a084ba404d
And a small writeup about the issue as a whole:
http://mojoapps.dk/?p=1
Note: When using the gist, don't forget to subscribe to the UIAccessibilityReduceTransparencyStatusDidChangeNotification notification to refresh the UI when the accessibility setting changes.
I hope that someone finds this useful. :-)

Here are a few ways to do this:
if objc_getClass("UIBlurEffect") != nil {
// UIBlurEffect exists
} else {
// UIBlurEffect doesn't exist
}
Or with access to the blurEffect class
if let blurEffect: AnyClass = NSClassFromString("UIBlurEffect") {
// UIBlurEffect exists
} else {
// UIBlurEffect doesn't exist
}
There's actually a duplicate question here, although the title probably doesn't make it easy to find: How do I do weak linking in Swift?
-- Edit --
Misunderstood the question. There appears to be no way to find this out short of checking the device name:
Detect if device properly displays UIVisualEffectView?
I would rely on whatever Apple's fallback implementation is. It looks like they still apply the tint color so that is probably fine in most cases. If you really must change the appearance when no blur will be applied, I think device name is not terrible since they specifically outlined which devices do not support blur.

Related

How to detect that haptic feedback is disabled on iOs device?

I want to to show message in my application when haptic feedback is disabled in phone settings. How to detect that haptic feedback is disabled in device settings?
It's kludgy, but might this work?
- (BOOL)isHapticFeedbackDisabled {
BOOL result = NO;
UISelectionFeedbackGenerator *feedbackGenerator = [[UISelectionFeedbackGenerator alloc] init];
[feedbackGenerator prepare];
if ([feedbackGenerator.description containsString:#"prepared=0"]) result = YES;
feedbackGenerator = nil;
return result;
}
There is no way to check Haptic Feedback is enabled/disabled but there is private int _feedbackSupportLevel in UIKit for checking if device supports it:
func logFeedbackSupported() {
let supportLevel = UIDevice.current.value(forKey: "_feedbackSupportLevel")
print(supportLevel ?? "")
}
0: Not available,
1: First generation available (< iPhone 7),
2: Second generation available.
I advise you not to use Apples private APIs because:
The API could be changed in any version without you knowing about it.
Apple is parsing your app code to find out if you're using private API so be aware. Your app could be rejected.

Check if device supports UIFeedbackGenerator in iOS 10

In iOS 10, there is a new api which allows developers to make use of the taptic engine, UIFeedbackGenerator.
While this api is available in iOS 10, it only works on the new devices, iPhone 7 and 7 plus. It does not works on older devices including the 6S or 6S Plus, even those have a taptic engine. I guess the taptic engine on the 7 and 7 plus is a different more powerful one.
I can't seem to find a way to see if the device supports using the new api. I would like to replace some vibrate code with taptic code, where it makes sense.
Edit:
Adding the 3 concrete subclasses for search purposes:
UIImpactFeedbackGenerator
UINotificationFeedbackGenerator
UISelectionFeedbackGenerator
Edit 2:
I have a theory but no iPhone 7 device to test it so if you have one, give it a shot. UIFeedbackGenerator has a methods called prepare(). When printing out an instance of UIImpactFeedbackGenerator, I noticed that it printed a property named "prepared" which would show 0. Calling prepare() in simulator or on iPhone 6S and then printing out the instance still shows prepared as 0. Can someone call prepare() on an instance of UIImpactFeedbackGenerator from an iPhone7 and then print the instance to console to see if prepared is set to 1? This value is not exposed but there may be a way to get this info w/o using private apis.
So, apparently this can be done with a private API call.
Objective-C:
[[UIDevice currentDevice] valueForKey:#"_feedbackSupportLevel"];
Swift:
UIDevice.currentDevice().valueForKey("_feedbackSupportLevel");
... These methods seem to return:
0 = Taptic not available
1 = First generation (tested on an iPhone 6s) ... which does NOT support UINotificationFeedbackGenerator, etc.
2 = Second generation (tested on an iPhone 7) ... which does support it.
Unfortunately, there are two caveats here:
Using these could get your app rejected by Apple during the App Store's App Review, but there doesn't seem to be any other way currently.
We don't know what the actual values represent.
Special thanks to Tim Oliver and Steve T-S for helping test this with different devices. https://twitter.com/TimOliverAU/status/778105029643436033
Currently, the best way is to check the device's model using:
public extension UIDevice
public func platform() -> String {
var sysinfo = utsname()
uname(&sysinfo) // ignore return value
return String(bytes: Data(bytes: &sysinfo.machine, count: Int(_SYS_NAMELEN)), encoding: .ascii)!.trimmingCharacters(in: .controlCharacters)
}
}
The platform names for iPhone 7 and 7 plus are: "iPhone9,1", "iPhone9,3", "iPhone9,2", "iPhone9,4"
Source: iOS: How to determine the current iPhone/device model in Swift?
You can create a function:
public extension UIDevice {
public var hasHapticFeedback: Bool {
return ["iPhone9,1", "iPhone9,3", "iPhone9,2", "iPhone9,4"].contains(platform())
}
}
I have extended chrisamanse's answer. It extraxts the generation number from the model identifier and checks if it is equal or greater than 9. Should work with future iPhone models unless Apple decides to introduce a new internal naming scheme.
public extension UIDevice {
var modelIdentifier: String {
var sysinfo = utsname()
uname(&sysinfo) // ignore return value
return String(bytes: Data(bytes: &sysinfo.machine, count: Int(_SYS_NAMELEN)), encoding: .ascii)!.trimmingCharacters(in: .controlCharacters)
}
var hasHapticFeedback: Bool {
// assuming that iPads and iPods don't have a Taptic Engine
if !modelIdentifier.contains("iPhone") {
return false
}
// e.g. will equal to "9,5" for "iPhone9,5"
let subString = String(modelIdentifier[modelIdentifier.index(modelIdentifier.startIndex, offsetBy: 6)..<modelIdentifier.endIndex])
// will return true if the generationNumber is equal to or greater than 9
if let generationNumberString = subString.components(separatedBy: ",").first,
let generationNumber = Int(generationNumberString),
generationNumber >= 9 {
return true
}
return false
}
}
Use it like so:
if UIDevice.current.hasHapticFeedback {
// work with taptic engine
} else {
// fallback for older devices
}
class func isFeedbackSupport() -> Bool {
if let value = UIDevice.current.value(forKey: "_feedbackSupportLevel") {
let result = value as! Int
return result == 2 ? true : false
}
return false
}

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

How do I know if cellular access for my iOS app is disabled?

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

Resources