Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vibrate using iOS haptics engine on supported devices (3.x) #60398

Merged
merged 1 commit into from
Apr 27, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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