1

In an iOS app, I have an interaction where I want to play both a tap sound:

AudioServicesPlaySystemSound(1104)

and a light haptic:

self.feedbackGenerator = [[UISelectionFeedbackGenerator alloc] init];
[self.feedbackGenerator prepare];
//then later
[self.feedbackGenerator selectionChanged];

This works fine, except when the user's Sounds->System Haptics setting is off. In that scenario, the sound plays much louder than it should. If I remove the haptic call from the code, the sound plays normally.

I've confirmed the above on an iPhone 11 Pro/iOS 14.4.1, but I have reports of this on other iOS 14 iPhones. One tester with iOS 13 didn't experience the problem.

Order of the sound/haptic doesn't change the result.

If I delay one or the other by 0.25s everything works fine, but that delay is too lengthy (doesn't sound or feel right). If I use a very short delay (like 0.01s) the problem occurs.

Per iOS: How to check is Haptic Feedback enabled (iOS Settings), there is no ability to detect the status of the System Haptics setting, else I'd skip the haptic step if disabled.

Apple provides this, indicating sounds and haptics should work together:

If you want to include sound along with the haptic feedback, you need to manually play the sound and sync it with the haptics. - https://developer.apple.com/documentation/uikit/uifeedbackgenerator

Any idea what's going on here? Expected behavior? iOS bug? Workaround?

Mitch Cohen
  • 1,551
  • 3
  • 15
  • 29
  • "If you want to include sound along with the haptic feedback, you need to manually play the sound and sync it with the haptics" Yes, but they aren't talking about something so crude as AudioServicesPlaySystemSound. – matt Mar 19 '21 at 19:19
  • I recognize that. If I can find a way to reproduce the standard 1104 sound using another method (without bundling a static version of it), I'm happy to do so. – Mitch Cohen Mar 19 '21 at 19:24
  • Yeah, I thought of that of course. – matt Mar 19 '21 at 19:25
  • I think I've solved it, just after posting of course. AudioServicesPlaySystemSoundWithCompletion seems to avoid the issue (playing the haptic upon completion). Running some tests to confirm. – Mitch Cohen Mar 19 '21 at 19:27
  • Oh yeah, if you're leaving out the completion handler and failing to deallocate the sound, that was always a bug. See https://github.com/mattneub/Programming-iOS-Book-Examples/blob/master/bk2ch14p640systemSound/ch27p910systemSound/ViewController.swift (the _last_ code block on that page) for the right way to play system sound. – matt Mar 19 '21 at 19:27
  • Thanks. I'm not sure it's necessary to create (then remove/dispose) of a SystemSoundID just to play a built-in sound. But I'll add that to be sure. At some point I'll come back and try without to see if anything leaks. – Mitch Cohen Mar 19 '21 at 19:42

1 Answers1

1

I've worked around this by using: AudioServicesPlaySystemSoundWithCompletion() instead of AudioServicesPlaySystemSound()

Specifically I'm using:

SystemSoundID soundId = 1104;
AudioServicesPlaySystemSoundWithCompletion(soundId, ^{
    [self.feedbackGenerator selectionChanged];
    AudioServicesRemoveSystemSoundCompletion(soundId);
    AudioServicesDisposeSystemSoundID(soundId);
});

The haptic is delayed by the duration of the sound, but it's a very short sound so not terribly noticeable in this use case.

(I'm not convinced it's necessary to create then destroy a SystemSoundID for a built-in sound, but taking the safe approach here.)

AudioServicesPlaySystemSoundWithCompletion was added in iOS 9. The original code here (other than the haptics) dates back to iOS 3 or 4. The headers say AudioServicesPlaySystemSound "will be deprecated in a future release. Use AudioServicesPlaySystemSoundWithCompletion instead." I wish they'd have actually deprecated it so I'd notice such a method even existed... The newer version is certainly more useful.

Mitch Cohen
  • 1,551
  • 3
  • 15
  • 29