diff --git a/doc/classes/Input.xml b/doc/classes/Input.xml index 4f0cc4adfbb1..3672d5cbad75 100644 --- a/doc/classes/Input.xml +++ b/doc/classes/Input.xml @@ -401,7 +401,8 @@ 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. diff --git a/platform/iphone/ios.h b/platform/iphone/ios.h index e926ec34b56a..05ecccf61a53 100644 --- a/platform/iphone/ios.h +++ b/platform/iphone/ios.h @@ -32,15 +32,26 @@ #define IOS_H #include "core/object.h" +#import 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; diff --git a/platform/iphone/ios.mm b/platform/iphone/ios.mm index 8c9f5cbae819..d6cc3df43249 100644 --- a/platform/iphone/ios.mm +++ b/platform/iphone/ios.mm @@ -33,13 +33,111 @@ #import "app_delegate.h" #import "view_controller.h" +#import #import #include 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 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]; diff --git a/platform/iphone/os_iphone.mm b/platform/iphone/os_iphone.mm index 4936eed0e674..87dea7698d54 100644 --- a/platform/iphone/os_iphone.mm +++ b/platform/iphone/os_iphone.mm @@ -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) {