First of all, in order to play a sound in background your application must be able to launch the related code responding to a callback event when in the background. Only specific app types are allowed to respond to callbacks events in the background. For example, the "Tile" application can play sound in background responding to BLE-related callbacks events which are fired when the application is in background (e.g., BLE peripheral connections and disconnections) . Conversely, a simple timer is suspended when application goes in background thus the timer-expiration callback won't be triggered and there is no way to launch any code to play sound in this case. (You can read here for some details and a possibile approach to build alarm clock in iOS)
If your application type is within the set of special background-mode applications you can play a sound responding to events in background even when the phone is in silent mode by using AVAudioPlayer. In your view controller import the AVFoundation framework:
import AVFoundation
than declare an AVAudioPlayer variable
var player: AVAudioPlayer?;
in order to play an mp3 file placed in the app bundle:
func playSound() {
guard let url = Bundle.main.url(forResource: "MY_MP3_FILE", withExtension: "mp3") else {
print("error");
return;
}
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback, with: AVAudioSessionCategoryOptions.mixWithOthers);
player = try AVAudioPlayer(contentsOf: url);
guard let player = player else {
print("error");
return;
}
player.play();
} catch let error {
print(error.localizedDescription);
}
}
Important: in Xcode in the "Capabilities" tab you also have to enable the "Audio, AirPlay and Picture in Picture" feature in the "Background Modes" section.

(Swift 4 solution tested in iOS 11 and 12 with different iPhone models).
Warning: Following this approach the sound will play at the current volume set in the phone, thus if the sound volume is equal to zero the sound will not be played. You can force the volume sound to a given value programmatically in this way:
let volumeView = MPVolumeView();
if let view = volumeView.subviews.first as? UISlider{
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.01) {
view.value = 0.5; //Set the volume level between 0 and 1
}
}
however, take into account that this approach shows the system sound volume bar and it could lead to a bad user experience.