I am trying to create a simple Soundboard in Swift with a Collection View, each button representing a sound that can be played. The structure is as follows (I know, it is probably not the smartest way to do it, but it worked earlier until I added some more sounds): I have a SoundFiles.swift with the SoundFiles class, which I declared
static let shared = SoundFiles()
It contains two arrays
let soundfiles: [String] = ["example_bla"]
let soundnames: [String] = ["example bla"]
as well as
var translation: [String: String] = [:]
var currentSoundfiles: [String] = []
where the "translation" dictionary is supposed to make a difference between the names of the files and what's being displayed on the screen, i.e. it matches the arrays "soundfiles" and "soundnames". The "currentSoundfiles" array takes care of the displayed sound files after a filter (search function) is applied. In my Tab Bar Controller, I have a View Controller containing
var soundPlayers: [Sound?] = []
override func viewDidLoad() {
super.viewDidLoad()
setUpView()
setupSwiftySound()
setupDismissKeyboard()
SoundFiles.shared.currentSoundfiles = SoundFiles.shared.soundfiles
SoundFiles.shared.findTranslation()
fillDropDowns()
fillSounds()
}
override func viewDidAppear(_ animated: Bool) {
refreshCollectionView()
}
where
func fillSounds(){
soundPlayers.removeAll()
for (index, _) in SoundFiles.shared.currentSoundfiles.enumerated(){
if let playingURL = Bundle.main.url(forResource: SoundFiles.shared.currentSoundfiles[index], withExtension: "wav"){
soundPlayers.append(Sound(url: playingURL))
soundPlayers[index]?.volume = SoundFiles.shared.volume
}
}
}
is the only relevant function. As long as I include 49 or fewer sound files, everything works perfectly fine. Including 50 or more sound files, the following error/warning occurs multiple times:
SwiftySound initialization error: Error Domain=NSOSStatusErrorDomain Code=-42 "(null)"
What is weird is that I can still run the first 49 sound files without a problem (clicking on the other buttons just doesn't do anything), but any other action makes the app crash, e.g. trying to change to a different View Controller of the app or retrieving additional information of the sound file by clicking on a "+" next to the button (implemented with a DropDown Menu). The crash error when trying to go to the second View Controller Tab reads as follows:
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Could not load NIB in bundle: 'NSBundle (loaded)' with name 'fGY-5H-E9k-view-obO-1i-lrO' and directory 'SbOne.storyboardc'' *** First throw call stack: (0x185e48ec4 0x185019a50 0x185d4f594 0x1b2c8fea8 0x1b2a208e0 0x1b2a2128c 0x1b2a21554 0x1b298dea8 0x1b298e1b0 0x1b298f140 0x1b2990440 0x1b2972630 0x1b349177c 0x18a444b7c 0x18a449b34 0x18a3a8598 0x18a3d6ec8 0x18a3d7d30 0x185dd87cc 0x185dd3460 0x185dd3a00 0x185dd31f0 0x18804c584 0x1b2fe8c00 0x102474838 0x185892bb4) libc++abi.dylib: terminating with uncaught exception of type NSException (lldb)
and XCode brings me to the AppDelegate.swift showing
Thread 1: signal SIGABRT
The problem is independent of which sound files are included, it just depends on the number. Does anybody have an idea of whats happening here?
As @DavidPasztor pointed out, the problem was a memory issue because I opened too many sound files at once. Reducing the playersPerSound
property to 1 indeed solved the problem but it would, of course, reoccur at some point. How I solved the issue for me now is as follows:
I kept 5 players per sound to be able to play sounds more often than once before they are finished (important for my other Storyboards/View Controllers, where the user is supposed to choose a shorter list of sounds from the first Storyboard/View Controller for a quick selection). In the first Storyboard/View Controller, I got rid of the fillSounds
function and instead used the following code when a sound is supposed to be played:
@objc func playSound(sender: UIButton){
if soundPlayers.count >= 20 {
soundPlayers.removeLast(10)
}
if let playingURL = Bundle.main.url(forResource: SoundFiles.shared.currentSoundfiles[sender.tag], withExtension: "wav"){
soundPlayers.append(Sound(url: playingURL))
soundPlayers[soundPlayers.count-1]?.volume = SoundFiles.shared.volume
soundPlayers[soundPlayers.count-1]?.play()
}
}
This code snippet now doesn't make use of the 5 players per sound but instead adds a new player for the same sound if it is played again and deletes the first 10 players if the size reaches 20. The other Storyboards/View Controllers stay untouched with the restriction that the number of sounds on the board cannot exceed 20 (or any other reasonable number). Up to now, I didn't encounter any problem with the App in that way.