Browse Source

Vibrate using iOS haptics engine on supported devices

Timo Schwarzer 3 years ago
parent
commit
4d3a7ad6ca
3 changed files with 116 additions and 2 deletions
  1. 11 0
      platform/iphone/ios.h
  2. 99 0
      platform/iphone/ios.mm
  3. 6 2
      platform/iphone/os_iphone.mm

+ 11 - 0
platform/iphone/ios.h

@@ -32,15 +32,26 @@
 #define IOS_H
 
 #include "core/object/class_db.h"
+#import <CoreHaptics/CoreHaptics.h>
 
 class iOS : public Object {
 	GDCLASS(iOS, Object);
 
 	static void _bind_methods();
 
+private:
+	CHHapticEngine *haptic_engine API_AVAILABLE(ios(13)) = nullptr;
+
+	CHHapticEngine *get_haptic_engine_instance() API_AVAILABLE(ios(13));
+	void start_haptic_engine();
+	void stop_haptic_engine();
+
 public:
 	static void alert(const char *p_alert, const char *p_title);
 
+	bool supports_haptic_engine();
+	void vibrate_haptic_engine(float p_duration_seconds);
+
 	String get_model() const;
 	String get_rate_url(int p_app_id) const;
 

+ 99 - 0
platform/iphone/ios.mm

@@ -32,11 +32,110 @@
 
 #import "app_delegate.h"
 #import "view_controller.h"
+
+#import <CoreHaptics/CoreHaptics.h>
 #import <UIKit/UIKit.h>
 #include <sys/sysctl.h>
 
 void iOS::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("get_rate_url", "app_id"), &iOS::get_rate_url);
+	ClassDB::bind_method(D_METHOD("supports_haptic_engine"), &iOS::supports_haptic_engine);
+	ClassDB::bind_method(D_METHOD("start_haptic_engine"), &iOS::start_haptic_engine);
+	ClassDB::bind_method(D_METHOD("stop_haptic_engine"), &iOS::stop_haptic_engine);
+};
+
+bool iOS::supports_haptic_engine() {
+	if (@available(iOS 13, *)) {
+		id<CHHapticDeviceCapability> capabilities = [CHHapticEngine capabilitiesForHardware];
+		return capabilities.supportsHaptics;
+	}
+
+	return false;
+}
+
+CHHapticEngine *iOS::get_haptic_engine_instance() API_AVAILABLE(ios(13)) {
+	if (haptic_engine == nullptr) {
+		NSError *error = nullptr;
+		haptic_engine = [[CHHapticEngine alloc] initAndReturnError:&error];
+
+		if (!error) {
+			[haptic_engine setAutoShutdownEnabled:true];
+		} else {
+			haptic_engine = nullptr;
+			NSLog(@"Could not initialize haptic engine: %@", error);
+		}
+	}
+
+	return haptic_engine;
+}
+
+void iOS::vibrate_haptic_engine(float p_duration_seconds) API_AVAILABLE(ios(13)) {
+	if (@available(iOS 13, *)) { // We need the @available check every time to make the compiler happy...
+		if (supports_haptic_engine()) {
+			CHHapticEngine *haptic_engine = get_haptic_engine_instance();
+			if (haptic_engine) {
+				NSDictionary *hapticDict = @{
+					CHHapticPatternKeyPattern : @[
+						@{CHHapticPatternKeyEvent : @{
+							CHHapticPatternKeyEventType : CHHapticEventTypeHapticTransient,
+							CHHapticPatternKeyTime : @(CHHapticTimeImmediate),
+							CHHapticPatternKeyEventDuration : @(p_duration_seconds)
+						},
+						},
+					],
+				};
+
+				NSError *error;
+				CHHapticPattern *pattern = [[CHHapticPattern alloc] initWithDictionary:hapticDict error:&error];
+
+				[[haptic_engine createPlayerWithPattern:pattern error:&error] startAtTime:0 error:&error];
+
+				NSLog(@"Could not vibrate using haptic engine: %@", error);
+			}
+
+			return;
+		}
+	}
+
+	NSLog(@"Haptic engine is not supported in this version of iOS");
+}
+
+void iOS::start_haptic_engine() {
+	if (@available(iOS 13, *)) {
+		if (supports_haptic_engine()) {
+			CHHapticEngine *haptic_engine = get_haptic_engine_instance();
+			if (haptic_engine) {
+				[haptic_engine startWithCompletionHandler:^(NSError *returnedError) {
+					if (returnedError) {
+						NSLog(@"Could not start haptic engine: %@", returnedError);
+					}
+				}];
+			}
+
+			return;
+		}
+	}
+
+	NSLog(@"Haptic engine is not supported in this version of iOS");
+}
+
+void iOS::stop_haptic_engine() {
+	if (@available(iOS 13, *)) {
+		if (supports_haptic_engine()) {
+			CHHapticEngine *haptic_engine = get_haptic_engine_instance();
+			if (haptic_engine) {
+				[haptic_engine stopWithCompletionHandler:^(NSError *returnedError) {
+					if (returnedError) {
+						NSLog(@"Could not stop haptic engine: %@", returnedError);
+					}
+				}];
+			}
+
+			return;
+		}
+	}
+
+	NSLog(@"Haptic engine is not supported in this version of iOS");
 }
 
 void iOS::alert(const char *p_alert, const char *p_title) {

+ 6 - 2
platform/iphone/os_iphone.mm

@@ -298,8 +298,12 @@ String OSIPhone::get_processor_name() const {
 }
 
 void OSIPhone::vibrate_handheld(int p_duration_ms) {
-	// iOS does not support duration for vibration
-	AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
+	if (ios->supports_haptic_engine()) {
+		ios->vibrate_haptic_engine((float)p_duration_ms / 1000.f);
+	} else {
+		// iOS <13 does not support duration for vibration
+		AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
+	}
 }
 
 bool OSIPhone::_check_internal_feature_support(const String &p_feature) {