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
}