14

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.

Prodigga
  • 1,457
  • 1
  • 22
  • 37
  • That's bad news - have you tried it on the 9.1 beta? – Rhythmic Fistman Sep 28 '15 at 01:05
  • No, we are adding 3D touch capability to an app we are going to be releasing in a few days, so we are aiming for 9.0. Using Xcode 7.0. – Prodigga Sep 28 '15 at 01:07
  • Maybe I am doing something wrong. – Prodigga Sep 28 '15 at 01:09
  • From what I understand, you are on the right track with accessing the trait collection and using `forceTouchCapability`. This link seems helpful/relevant: http://pinkstone.co.uk/how-to-use-3d-touch-in-ios-9-part-1-peek-and-pop/ – Hayley Guillou Sep 28 '15 at 01:14
  • On what basis are you stating that `forceTouchCapability` only checks to see if the device is iOS 9 or greater? – rmaddy Sep 28 '15 at 01:56
  • We have 3 devices - an iPad Air 1 (No 3d touch, running iOS 9.0), iPhone 6 (No 3d touch, running iOS 8.3), iPhone 6s (3d touch, running iOS 9.0). – Prodigga Sep 28 '15 at 01:59
  • forceTouchCapability returns: true on iPad, true on iPhone 6s, false on iPhone 6. – Prodigga Sep 28 '15 at 02:00
  • I am writing a plugin for a Unity3D game. To get access to the Unity View, I am fetching the last view using this: UIView *UnityView = [[[[UIApplication sharedApplication] keyWindow] subviews] lastObject]; and returning this: [[UnityView traitCollection] forceTouchCapability]; – Prodigga Sep 28 '15 at 02:02
  • I think I just realised my error - I am returning forceTouchCapability, assuming it is a 'bool'. It is actually an enum, and I guess it was being cast to a bool - returning true when it equalled '1' (UIForceTouchCapabilityUnavailable = 1) – Prodigga Sep 28 '15 at 02:06
  • I was just going to point out that the return value is not a `BOOL`. – rmaddy Sep 28 '15 at 02:09
  • Amateur mistake :-) I've answered my own question – Prodigga Sep 28 '15 at 02:10

6 Answers6

24

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;
rmaddy
  • 314,917
  • 42
  • 532
  • 579
Prodigga
  • 1,457
  • 1
  • 22
  • 37
  • Keep in mind that you need to properly guard this code if your app supports iOS 8.x or earlier in addition to iOS 9. – rmaddy Sep 28 '15 at 02:12
  • Yep! Thanks, already done :) Ran into that properly already hehe. – Prodigga Sep 28 '15 at 02:14
  • swift version http://stackoverflow.com/questions/33686640/swift-check-if-3d-touch-is-possible?lq=1 – Irfan Gul Nov 13 '15 at 06:41
  • @Prodigga hey I do not have a 3D touch device I wanted to ask: Although it returns NotAvailable for device not supporting 3D Touch, does it also return NotAvailable for devices with 3D touch but disabled in settings? – ShayanK May 28 '16 at 12:20
12

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");
    }
}
Iulian Onofrei
  • 9,188
  • 10
  • 67
  • 113
amleszk
  • 6,192
  • 5
  • 38
  • 43
3

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
}
Valentin Shergin
  • 7,166
  • 2
  • 50
  • 53
3
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.

Aditya
  • 1,136
  • 2
  • 12
  • 19
0

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
      }
    }
  }
Gurjinder Singh
  • 9,221
  • 1
  • 66
  • 58
0

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
}
ingconti
  • 10,876
  • 3
  • 61
  • 48