Learn Courses My Dashboard

Stopwatch iPad App that can play a sound when it reaches the last recorded time

Hi All,
I’m trying to build a simple stopwatch app that I will use to time pool players when they play a 6 ball shootout (players break off and clear 6 balls in the quickest time).
The stopwatch part itself I have working fine with buttons for start, stop, reset and penalty (adds 5 seconds penalty to the time) and I have added the code to run a sound using AVAudioPlayer but the part I’m really stuck with is what code to put in to automatically play the sound when the timer reaches the same time of the first players time.
For example: player 1 breaks and clears all 6 balls in a time of 00:45:22 and when player 2 plays and their time reaches 00:45:22 the sound will play by itself without the need for manual button.
Currently I only know how to get Xcode to play a sound at a set time.
I would really appreciate any help with this as I’m very much a beginner with Xcode and a bit stumped after not finding anything specific to what I need online.
I’ve attached an image of my app so far. The top one is the running stopwatch and the player name to the left and under that I have the other player and also tried putting in a Text Field which I was trying to enter the first players time manually then reset the stopwatch and have the sound play when it reached the time I entered into the Text Field which hasn’t quite worked.
Here’s my code too.
Thanks.

//
//  ViewController.swift
//  6 Ball Shootout Timer
//
//  Created by Marcus Allen on 17/01/2023.
//

import UIKit
import AVFoundation
import SwiftUI
import ClockKit

class ViewController: UIViewController, UITextFieldDelegate{

var timer = Timer()
var (hours, minutes, seconds, fractions) = (0, 0, 0, 0)
var audioPlayer = AVAudioPlayer()

@IBOutlet weak var bg: UIImageView!
@IBOutlet weak var timerLabel: UILabel!
@IBOutlet weak var fractionsLabel: UILabel!
@IBOutlet weak var startOutlet: UIButton!
@IBOutlet weak var stopOutlet: UIButton!
@IBOutlet weak var resetOutlet: UIButton!
@IBOutlet weak var penaltyOutlet: UIButton!
@IBOutlet weak var timeToBeat: UITextField!

@IBAction func start(_ sender: UIButton) {
    timer = Timer.scheduledTimer(timeInterval: 0.01, target: self, selector: #selector(ViewController.keepTimer), userInfo: nil, repeats: true)
    startOutlet.isHidden = true
}

@IBAction func stop(_ sender: UIButton) {
    timer.invalidate()
    startOutlet.isHidden = false
}

@IBAction func reset(_ sender: UIButton) {
    (hours, minutes, seconds, fractions) = (0, 0, 0, 0)
    timerLabel.text = "00:00"
    fractionsLabel.text = ":00"
}
@IBAction func penalty(_ sender: UIButton) {
    seconds += 5
}

@IBAction func playerName(_ sender: UITextField) {
    sender.resignFirstResponder()
}
override func viewDidLoad(){
    super.viewDidLoad()
    
    bg.loadGif(name: "vid2")
    
    do
    {let audioPath = Bundle.main.path(forResource: "endgame", ofType: ".mp3")
        try audioPlayer = AVAudioPlayer(contentsOf: URL(fileURLWithPath: audioPath!))
    }
    catch {
        //ERROR
        
    }    }

@objc func keepTimer() {
    fractions += 1
    if fractions > 99 {
        seconds += 1
        fractions = 0
    }
    
    
    if seconds == 60 {
        minutes += 1
        seconds = 0
    }
    
    
    let secondsString = seconds > 9 ? "\(seconds)" : "0\(seconds)"
    let minutesString = minutes > 9 ? "\(minutes)" : "0\(minutes)"
    
    timerLabel.text = "\(minutesString):\(secondsString)"
    fractionsLabel.text = ":\(fractions)"    }

}

@mwr_allen

It might be easier if you share your project so that we can have a good look at what it is doing in the context that you have created.

Can you compress the entire project into a zip file and post that to either Dropbox or Google Drive. Then create a share link and post that link in a reply.

If you choose to use Google Drive then when you create the Share link, ensure that the “General Access” option is set to “Anyone with the Link” rather than “Restricted”.

1 Like

Hi there,

Thank you for your reply.

Here is the file.https://drive.google.com/file/d/1Uiep-n-Dhxud_NNeTA-SeqdTjVGsxOP2/view?usp=sharing

I have started going on a slightly different route with two separate timers which I remember having issues with before with button 1 affecting both timers but trying to do it this way so that the sound function has a reference to play from.

Thanks,

Marcus.

Having two timers means that you have to have two separate variable structures which you do have apart from where you are populating your timerLabel.text and timer2label.text. You have to do them separately in their respective keepTimer functions.

1 Like

Ok, so I need to move this
timer2Label.text = “(minutes2String):(seconds2String)”
fractions2Label.text = “:(fractions2)”
To below the } to separate it?

ok, I’ve moved the code and now runs fine separately.
next part I’m stuck on is how to fire the sound at the right time.
How can I fire a sound if the second timer (timer2Label) reaches the same time that the first timer (timerLabel) has reached?
I would put in a simple button to fire the sound but need it to fire automatically.
Thanks.

@mwr_allen

My suggestion is to create a SoundManager file like this:

import Foundation
import AVFoundation

class SoundManager {

    var audioPlayer: AVAudioPlayer?

    func playSound() {

        let soundFilename = "endgame"

        //  Get path to resource
        let filePath = Bundle.main.path(forResource: soundFilename, ofType: "mp3")

        //  Check that it is not nil
        guard filePath != nil else {
            print("Couldn't find sound file \(soundFilename) in the bundle")
            return
        }

        //  Create a URL object from the bundlePath string.
        let soundUrl = URL(fileURLWithPath: filePath!)

        do {
            //  Create audio player object
            audioPlayer = try AVAudioPlayer(contentsOf: soundUrl)

            //  Play the sound effect
            audioPlayer?.play()

        } catch {
            print("Could not create the audio player object for the sound file \(soundFilename)")
            return
        }
    }
}

Then in your ViewController file add a declaration to create an instance of that SoundManager like this:

var soundPlayer = SoundManager()

In your code create a function to check for the times being equal by doing something like this:

    func checkForEndGame() {

        if hours1 == hours2 && minutes1 == minutes2 && seconds1 == seconds2 && fractions1 == fractions2 {
            timer1.invalidate()
            timer2.invalidate()

            soundPlayer.playSound()
        }
    }

(Note that I have referred to the first timer variables as timer1, hours1, minutes1, seconds1, and fractions1 etc in the interests of being consistent.)

Trigger the checkForEndGame function from both your keepTimer1 and keepTimer2 by calling checkForEndGame() as the last instruction in each of those functions.

1 Like

Got it working now, had to rework the code a little but now working fine.
Thank you for the help.