I'd like to use react-native-blur on my iOS app with React Native, using an alternative visualization on iOS 7 (such as a backgroundColor), which does not support UIVisualEffectView.
E.g.:
styles = {
backgroundColor: isVisualEffectViewSupported ? "transparent" : "rgba(0,0,0,.5)"
}
How can I detect if UIVisualEffectView is supported by the current platform?
Objective-C:
Class cls = NSClassFromString(#"UIVisualEffectView");
if (cls) {
// class exists, do whatever you need with it
} else {
// class doesn't exists, fallback
}
Swift:
if NSClassFromString("UIVisualEffectView") != nil {
println("UIVisualEffectView exists")
} else {
println("UIVisualEffectView does not exists")
}
Just write a native module which export iOS version to JS, then JS detect the version and decide if UIVisualEffectView is supported.
Related
I have a iOS app that uses a library. This library used to have problems when the iOS or iPadOS device or simulator was set to be in Zoom mode in the system settings. The developer of the library has provided a fix, but he has told me that in order to benefit from the fix, I have to use UIScreen nativeScale instead of scale. I tried to solve the problem without affecting the rest of the project code by declaring an extension like this:
extension UIScreen {
open var scale: CGFloat {
get {
return nativeScale
}
}
}
This works, but I would like somehow to make this extension effective only when the device is set to zoom mode. I have found a way to detect that, but now I don't know how to proceed. I am aware that I cannot access the scale property from within its getter, so I was wondering if there is a possible implementation to achieve that. Thanks for your attention.
The comment from Cristik made me think of a solution. Cristik asked for the code to detect Zoom mode. I have found a good implementation in the DeviceKit repository. The solution is the following:
public var isZoomed: Bool? {
guard isCurrent else { return nil }
if Int(UIScreen.main.scale.rounded()) == 3 {
// Plus-sized
return UIScreen.main.nativeScale > 2.7 && UIScreen.main.nativeScale < 3
} else {
return UIScreen.main.nativeScale > UIScreen.main.scale
}
}
To achieve my goal, I added to DeviceKit this property:
public var originalDeviceScale: CGFloat? {
guard isCurrent else { return nil }
return UIScreen.main.scale
}
Being in a completely different target, my extension to UIScreen in the main project does not apply, so I can now write:
import UIKit
import DeviceKit
extension UIScreen {
open var scale: CGFloat {
get {
let device = Device()
guard let isZoomed = device.isZoomed else {
return -1
}
if isZoomed {
return nativeScale
}
guard let originalDeviceScale = device.originalDeviceScale else {
return -1
}
return originalDeviceScale
}
}
If you are not using DeviceKit, you could add a static library target to your project containing just the originalNativeScale property.
I understand this is not a complete solution but could be possibly useful to others. Thanks for your attention.
I am trying to determine whether the device being used is iPhone or iPad.
Please see this question: Detect current device with UI_USER_INTERFACE_IDIOM() in Swift
That solution works if you use UIKit. But
What is the equivalent method if you are using SwiftUI ?
You can find the device type, like this:
if UIDevice.current.userInterfaceIdiom == .pad {
...
}
You can use this:
UIDevice.current.localizedModel
In your case, an implementation method could be:
if UIDevice.current.localizedModel == "iPhone" {
print("This is an iPhone")
} else if UIDevice.current.localizedModel == "iPad" {
print("This is an iPad")
}
Obviously, you can use this for string interpolation such as this (assuming the current device type is an iPhone):
HStack {
Text("Device Type: ")
Text(UIDevice.current.localizedModel)
}
//Output:
//"Device Type: iPhone"
It will return the device type. (iPhone, iPad, AppleWatch) No further imports are necessary aside from SwiftUI which should have already been imported upon creation of your project if you selected SwiftUI as the interface.
NOTE: It does not return the device model (despite the ".localizedModel")
Hope this helps!
UIDevice is no longer available from WatchKit 2 on
WKInterfaceDevice could be helpful: https://developer.apple.com/documentation/watchkit/wkinterfacedevice
i.e.:
let systemName = WKInterfaceDevice.current().systemName
let systemVersion = WKInterfaceDevice.current().systemVersion
let deviceModel = WKInterfaceDevice.current().model
I tried to access the trait collection and check "forceTouchCapability", but "forceTouchCapability" simply checks to see if the device is iOS 9.0 or greater.
So, this means that on any device with iOS 9, force touch is 'available'. I need to a way to check if 3D touch is actually supported on the users device (iPhone 6s) and I need to make sure that the 3D Touch option is actually enabled in the accessibility settings.
I was accidentally casting forceTouchCapability to a BOOL (using it as a return value to my method that was set to return a boolean). I needed to check if forceTouchCapability was equal to UIForceTouchCapabilityAvailable.
Instead of:
return [[MyView traitCollection] forceTouchCapability];
I need:
return [[MyView traitCollection] forceTouchCapability] == UIForceTouchCapabilityAvailable;
If you implement this in UIViewController, the timing matters. Checking in viewDidLoad will return Unknown when it will return Available later in the lifecycle.
- (void)traitCollectionDidChange:(nullable UITraitCollection *)previousTraitCollection {
[self checkForForceTouch];
}
- (void)checkForForceTouch {
if ([self.traitCollection respondsToSelector:#selector(forceTouchCapability)] &&
self.traitCollection.forceTouchCapability == UIForceTouchCapabilityAvailable) {
NSLog(#"Force touch found");
}
}
Sometimes we want just to have forceTouchCapability value right now synchronously and don't want to wait for traitCollectionDidChange: event. In that case, we can use pretty stupid function like this:
public func forceTouchCapability() -> UIForceTouchCapability {
return UIApplication.sharedApplication().keyWindow?.rootViewController?.traitCollection.forceTouchCapability ?? .Unknown
}
if ([MyView respondsToSelector:#selector(traitCollection)] &&
[MyView.traitCollection respondsToSelector:#selector(forceTouchCapability)] &&
MyView.traitCollection.forceTouchCapability == UIForceTouchCapabilityAvailable) {
return YES;
}
My point is, if you are supporting iOS 7 and iOS 8 as well, remember to check for both the conditions: [MyView respondsToSelector:#selector(traitCollection)] and [MyView.traitCollection respondsToSelector:#selector(forceTouchCapability)].
If you keep the first check, the app works fine on iOS 7 but crashes on iOS 8.
Basically, Apple introduced traitCollection property in iOS 8 but added forceTouchCapability property only in iOS 9.
from UITraitCollection.h:
#property (nonatomic, readonly) UITraitCollection *traitCollection NS_AVAILABLE_IOS(8_0);
#property (nonatomic, readonly) UIForceTouchCapability forceTouchCapability NS_AVAILABLE_IOS(9_0);
PS: Learnt it the hard way, after app started to crash on App Store.
Swift 4.0 and 4.1.
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Check device supports feature
if self.traitCollection.forceTouchCapability == .available {
// Enable 3D Touch feature here
} else {
// Fall back to other non 3D feature
}
}
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
// Update the app's 3D Touch support
if self.traitCollection.forceTouchCapability == .available {
// Enable 3D Touch feature here
} else {
// Fall back to other non 3D feature
}
}
}
following Valentin Shergin, updated for swift 4.2 if You need a sync call:
func forceTouchCapability() -> UIForceTouchCapability {
return UIApplication.shared.keyWindow?.rootViewController?.traitCollection.forceTouchCapability ?? .unknown
}
func hasForceTouchCapability() -> Bool {
return forceTouchCapability() == .available
}
My app's deployment target is 7.0 . I want to use both UIAlertController and UIAlertView. I read somewhere that checking for iOS versions is not good, so i used this code :
if (NSClassFromString("UIAlertController") != nil) {
// UIAlertController
} else {
// UIAlertView
But even if do that, i still get that "correctable" error "UIAlertController is only available on iOS 8.0 or newer" and i have to choose between 3 'Fix-it' options :
Add 'if #available' version check ( if #available(iOS 8.0, *) { ... } else { ... })
Add #available attribute to enclosing instance method
Add #available attribute to enclosing class
What should i do ? Currently using Xcode 7 GM
As stated, the best way to do this is using the #available function. I've attached a code example for you.
if #available(iOS 8.0, *) {
} else {
}
#available is the best way to do these checks.
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
}