Skip to content
Open
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
6 changes: 4 additions & 2 deletions src/common/ios.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,11 @@ namespace ios
std::string getLoveInResources(bool &fused);

/**
* Causes devices with vibration support to vibrate for about 0.5 seconds.
* Causes devices with vibration support to vibrate for the specified duration.
* On iOS 13+, uses Core Haptics for precise duration control.
* On iOS < 13, falls back to legacy 0.5 seconds of vibration.
**/
void vibrate();
void vibrate(double seconds);

/**
* Enable mix mode (e.g. with background music apps) and playback with a muted device.
Expand Down
50 changes: 49 additions & 1 deletion src/common/ios.mm
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@

#import <AudioToolbox/AudioServices.h>
#import <AVFoundation/AVFoundation.h>
#import <CoreHaptics/CoreHaptics.h>

#include "modules/audio/Audio.h"

Expand Down Expand Up @@ -364,10 +365,57 @@ - (void)applicationBecameActive:(NSNotification *)note
return path;
}

void vibrate()
void vibrate(double seconds)
{
@autoreleasepool
{
if (@available(iOS 13.0, *))
{
NSError *error = nil;
CHHapticEngine *engine = [[CHHapticEngine alloc]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apple's haptic engine docs say to "Maintain a strong reference to it so it doesn’t go out of scope while the haptic is playing."

Should this code be changed to do that?

initAndReturnError:&error];

if (engine != nil)
{
[engine startAndReturnError:nil];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apple's docs say "This method blocks all subsequent event processing on the current thread until the engine has started." and they have an alternative async method. What sort of performance cost does using the sync method have versus the async one?


NSDictionary *hapticDict = @{
CHHapticPatternKeyEvent: @[@{
CHHapticPatternKeyEventType:
CHHapticEventTypeHapticContinuous,
CHHapticPatternKeyTime: @0.0,
CHHapticPatternKeyEventDuration: @(seconds),
CHHapticPatternKeyEventParameters: @[
// Correlates to Android's default high intensity vibration.
@{
CHHapticPatternKeyParameterID:
CHHapticEventParameterIDHapticIntensity,
CHHapticPatternKeyParameterValue: @1.0
},
// Medium sharpness, 0.0 is dull rumble while 1.0 is crisp tap.
@{
CHHapticPatternKeyParameterID:
CHHapticEventParameterIDHapticSharpness,
CHHapticPatternKeyParameterValue: @0.5
}
]
}]
};

CHHapticPattern *pattern = [[CHHapticPattern alloc]
initWithDictionary:hapticDict error:nil];

if (pattern != nil)
{
id<CHHapticPatternPlayer> player =
[engine createPlayerWithPattern:pattern error:nil];
[player startAtTime:0 error:nil];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This (and earlier code) should probably have some form of error checking

return;
}
}
}

// Fallback: iOS < 13 or Core Haptics unavailable.
AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/modules/system/System.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ void System::vibrate(double seconds) const
#ifdef LOVE_ANDROID
love::android::vibrate(seconds);
#elif defined(LOVE_IOS)
love::ios::vibrate();
love::ios::vibrate(seconds);
#else
LOVE_UNUSED(seconds);
#endif
Expand Down
Loading