Skip to content

Commit

Permalink
Vibrate using iOS haptics engine on supported devices (3.x)
Browse files Browse the repository at this point in the history
  • Loading branch information
timoschwarzer authored and akien-mga committed Apr 27, 2022
1 parent ad76e07 commit 8fb9c96
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 3 deletions.
3 changes: 2 additions & 1 deletion doc/classes/Input.xml
Original file line number Diff line number Diff line change
Expand Up @@ -401,7 +401,8 @@
<argument index="0" name="duration_ms" type="int" default="500" />
<description>
Vibrate Android and iOS devices.
[b]Note:[/b] It needs [code]VIBRATE[/code] permission for Android at export settings. iOS does not support duration.
[b]Note:[/b] For Android, it requires enabling the [code]VIBRATE[/code] permission in the export preset.
[b]Note:[/b] For iOS, specifying the duration is supported in iOS 13 and later.
</description>
</method>
<method name="warp_mouse_position">
Expand Down
11 changes: 11 additions & 0 deletions platform/iphone/ios.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,26 @@
#define IOS_H

#include "core/object.h"
#import <CoreHaptics/CoreHaptics.h>

class iOS : public Object {
GDCLASS(iOS, Object);

static void _bind_methods();

private:
CHHapticEngine *haptic_engine API_AVAILABLE(ios(13)) = NULL;

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;

Expand Down
98 changes: 98 additions & 0 deletions platform/iphone/ios.mm
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,111 @@
#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 == NULL) {
NSError *error = NULL;
haptic_engine = [[CHHapticEngine alloc] initAndReturnError:&error];

if (!error) {
[haptic_engine setAutoShutdownEnabled:true];
} else {
haptic_engine = NULL;
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) {
NSString *title = [NSString stringWithUTF8String:p_title];
NSString *message = [NSString stringWithUTF8String:p_alert];
Expand Down
8 changes: 6 additions & 2 deletions platform/iphone/os_iphone.mm
Original file line number Diff line number Diff line change
Expand Up @@ -680,8 +680,12 @@ void register_dynamic_symbol(char *name, void *address) {
}

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) {
Expand Down

0 comments on commit 8fb9c96

Please sign in to comment.