the making of egg timer app

So how do you like you eggs?

* in the demo above, for the purpose of testing, the value of time set for each hardness selection is soft 3s, medium 4s, hard 7s, so that we can SWIFTly check the function of the app :)

If you are one of those who love a soft soy-seasoned ramen egg, this egg timer will help you nail that perfectly soft-boiled egg each single time so you can have all the ramen eggs you want at home, anytime you want it!

There are a few steps to figure out in order to make this app work if you want all these cute fancy little features such as a time countdown, a stop timer that has an alarm sound, etc., here’s a breakdown of our challenges:

  • main title “How do want your eggs?“ needs to switch to “So you like it ___ huh?“ (fill in with user’s hardness selection), then switch to “Alright, don’t choke on them!“ when the egg is finished, aka progress bar reaches to 100%, stop timer alarms comes on, and then when user clicks the stop timers, the title needs to be reset to the original title

  • 3 buttons of hardness level need to be separated from 3 egg images

  • buttons need to have a dim/flash effect when the user clicks it

  • the click of any egg hardness level should trigger the progress bar to start

  • the click should also trigger the timer countdown to start

  • the stop timer button and its image need to be made separately

  • the image of the stop timer should be hidden until the progress bar reaches 100%, but hidden again after the user clicks on the stop timer button

Here’s the actual process:

Here are my assets design:

Clean code with simple notes:

import UIKit
import AVFoundation

class ViewController: UIViewController {
    
    func stopSound() {
        player.stop()
    }
    
    var timer = Timer()
    var totalTime = 0
    var secondsPassed = 0
    var player: AVAudioPlayer!
    
    
    @IBOutlet weak var titleLabel: UILabel!
    @IBOutlet weak var timeLeftLabel: UILabel!
    @IBOutlet weak var progressBar: UIProgressView!
    @IBOutlet weak var stopTimerImage: UIImageView!
    
    // stopTimer
    
    @IBAction func stopTimerButton(_ sender: UIButton) {
        
        stopSound()
        stopTimerImage.isHidden = true
        progressBar.setProgress(0, animated: false)
        secondsPassed = 0
        titleLabel.text = "How do you like your eggs?"
        timeLeftLabel.isHidden = true
        titleLabel.alpha = 1
        
    }
    
    // progressBar
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        timeLeftLabel.isHidden = true
        progressBar.setProgress(0, animated: false)
        stopTimerImage.isHidden = true
        progressBar.layer.cornerRadius = 10
        progressBar.clipsToBounds = true
        
    }
    
    // eggs hardness button
    
    let eggTime: [String: Int] = ["Soft": 3, "Medium" : 4, "Hard" : 7]
    
    @IBAction func hardnessAction(_ sender: UIButton ) {
        
        sender.alpha = 0.5
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {sender.alpha = 1}
        
        if progressBar.progress == 0 {
            
            timer.invalidate()
            let hardness = sender.currentTitle!
            totalTime = eggTime[hardness]!
            progressBar.setProgress(0, animated: false)
            secondsPassed = 0
            titleLabel.text = "So you like it \(hardness) huh?"
            
            timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(updateTime), userInfo: nil, repeats: true)
            
        } else { print("Ain't no way") }
    }
    
    // declare the selector "updateTime" in a function @objc is Objective-C
    
    @objc func updateTime() {
        if secondsPassed < totalTime {
            
            secondsPassed += 1
            progressBar.setProgress(Float(secondsPassed) / Float(totalTime), animated: true)
            print(Float(secondsPassed) / Float(totalTime))
            timeLeftLabel.isHidden = false
            
            let secondsRemaining = (Float(totalTime) - Float(secondsPassed))
            
            timeLeftLabel.text = timeString(time: TimeInterval(secondsRemaining))
            
        } else {
            
            timer.invalidate()
            titleLabel.text = String("Alright, don't choke on them!")
            playSound(soundName: "alarm_sound")
            stopTimerImage.isHidden = false
            timeLeftLabel.text = ""
            
            // animated flashing effect
            
            UIView.animate(withDuration: 0.5, delay: 0.25, options: [.repeat, .autoreverse], animations: {
                self.titleLabel.alpha = 0
            }, completion: nil)
        }
    }
    
    // declare playSound and stopSound function
    
    func playSound(soundName: String) {
        let url = Bundle.main.url(forResource: soundName, withExtension: "mp3")
        self.player = try! AVAudioPlayer(contentsOf: url!)
        self.player.play()
        self.player.numberOfLoops = -1
        
    }
    
    // change time countdown format to 00:00 (min:sec)

    func timeString(time: TimeInterval) -> String {
        let minutes = Int(time) / 60 % 60
        let seconds = Int(time) % 60
        return String(format: "%02d:%02d", minutes, seconds)
    }
}

// The time was set to 3, 4, 7 for the testing purpose, to put the app to practical use, change back to below:
// let eggTime : [String : Int] = ["Soft": 300, "Medium" : 420, "Hard" : 720]

More detailed notes version:

// code breakdown

import UIKit
import AVFoundation //import AVFoundation to play sound

class ViewController: UIViewController { /// ViewController is a subclass of UIViewController
    
    // stopSound function

    func stopSound() { 
        player.stop()
    }
    
    // declare variables

    var timer = Timer()
    var totalTime = 0
    var secondsPassed = 0
    var player: AVAudioPlayer! // declare AVAudioPlayer
    
    // declare IBOutlets
    
    @IBOutlet weak var titleLabel: UILabel!
    @IBOutlet weak var timeLeftLabel: UILabel!
    @IBOutlet weak var progressBar: UIProgressView!
    @IBOutlet weak var stopTimerImage: UIImageView!
    
    // stopTimerButton and stopTimerImage are separated created to individually control what the stop button does and what it looks like
    // stopTimerImage needs to be hidden when the timer is not running, but appears when the timer is running
    
    @IBAction func stopTimerButton(_ sender: UIButton) { 
        
        stopSound() // when button is pressed, stop the sound
        stopTimerImage.isHidden = true // hide the stopTimerImage when the button is pressed
        progressBar.setProgress(0, animated: false) // reset the progress bar back to 0
        secondsPassed = 0 // reset the secondsPassed back to 0
        titleLabel.text = "How do you like your eggs?" // reset the titleLabel back to the original text
        timeLeftLabel.isHidden = true // hide the timeLeftLabel when the button is pressed
        titleLabel.alpha = 1 // reset the titleLabel alpha back to 1, this is to reset the flashing effect of the title if you have any
                            //I don't need this but it's good to have in case of future changes
        
    } // stopTimerButton is connected to the stopTimerImage and the stopSound function
    
    // progressBar
    
    // create a function to tell the app what to do with the progressBar
    override func viewDidLoad() { 
        super.viewDidLoad()
        
        timeLeftLabel.isHidden = true // hide the timeLeftLabel when the app is loaded cuz user hasn't clicked on anything yet
        progressBar.setProgress(0, animated: false) // set the progress bar to 0 when the app is loaded
        stopTimerImage.isHidden = true // hide the stopTimerImage when the app is loaded
        progressBar.layer.cornerRadius = 10 // just to make it look nicer
        progressBar.clipsToBounds = true // just to make it look nicer
        
    }
    
    // eggs hardness button
    
    // create a dictionary to store the time for each egg hardness
    let eggTime: [String: Int] = ["Soft": 3, "Medium" : 4, "Hard" : 7]
    
    // create a function to tell the app what to do when the user clicks on the egg hardness button
    @IBAction func hardnessAction(_ sender: UIButton ) { // sender is the button that is clicked
        
        // flashing effect when the button is clicked
        sender.alpha = 0.5
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {sender.alpha = 1}
        
        // This part could be a bit confusing to understand, just remember this is AFTER the user has clicked on egg hardness button
        // under that parameter, the progress bar should start progressing from 0, seconds passed also start from 0
        // the timer should be invalidated first to prevent multiple timers running at the same time
        // the hardness is the title of the button that is clicked, so it will be the key to get the value from the dictionary
        // the totalTime is the value of the key that is clicked, so it will be the total time for the timer
        // the progress bar should be set to 0, animated false to prevent the progress bar from animating when the button is clicked, it should start from 0, not from the previous progress
        // the secondsPassed should be set to 0, to prevent the timer from starting from the previous time
        // the title label should be set to the text that is shown when the button is clicked, in this case, it's "So you like it \(hardness) huh?"
        // the timer should be scheduled to start counting down from the total time, the selector is updateTime, userInfo is nil, repeats is true, so the timer will keep running until it's invalidated
        // the updateTime function is called to update the time

        if progressBar.progress == 0 {
            
            timer.invalidate()
            let hardness = sender.currentTitle!
            totalTime = eggTime[hardness]!
            progressBar.setProgress(0, animated: false)
            secondsPassed = 0
            titleLabel.text = "So you like it \(hardness) huh?"
            
            // scheduledTimer is a function that schedules a timer to run after a certain amount of time, in this case, it's 1.0 second
            timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(updateTime), userInfo: nil, repeats: true)
            
        } else { print("Ain't no way") } // if the progress bar is not 0, print "Ain't no way", this can be anything you want to print into the console just for testing purposes
                                        //this is to prevent the user from clicking on the egg hardness button when the timer is running, 
    }
    
    //declare the selector "updateTime" (from the above) in a function called "@objc" which is Objective-C language
    //it's used to tell the compiler that the function is accessible from Objective-C code
    
    @objc func updateTime() { 
        if secondsPassed < totalTime { // if the secondsPassed is less than the totalTime, the progress bar should keep progressing, the timeLeftLabel should show the time left
            
            secondsPassed += 1 // secondsPassed should keep increasing by 1, so the progress bar can keep progressing
            progressBar.setProgress(Float(secondsPassed) / Float(totalTime), animated: true) // the progress is calculated by dividing the secondsPassed by the totalTime
            print(Float(secondsPassed) / Float(totalTime)) // print the progress into the console, this is for testing purposes
            timeLeftLabel.isHidden = false // show the timeLeftLabel when the timer is running
            
            let secondsRemaining = (Float(totalTime) - Float(secondsPassed)) 
            
            timeLeftLabel.text = timeString(time: TimeInterval(secondsRemaining)) // the timeLeftLabel should show the time remaining in the format of 00:00 (min:sec)
            
        } else { 
            
            // if the secondsPassed is equal to the totalTime, the timer should be invalidated, 
            // the title label should show "Alright, don't choke on them!", the sound should play, 
            // the stopTimerImage should appear, the timeLeftLabel should be hidden
            
            timer.invalidate()
            titleLabel.text = String("Alright, don't choke on them!")
            playSound(soundName: "alarm_sound")
            stopTimerImage.isHidden = false
            timeLeftLabel.text = "" // reset the timeLeftLabel back to empty
            
            // animated flashing effect
            
            // the titleLabel should flash when the timer is done, the alpha should be 0.5, 
            //the duration is 0.5, the delay is 0.25, the options are repeat and autoreverse
            UIView.animate(withDuration: 0.5, delay: 0.25, options: [.repeat, .autoreverse], animations: {
                self.titleLabel.alpha = 0
            }, completion: nil) // the completion is nil, this is to make the flashing effect keep going until the user stops it

        } // close else statement

    } // Close updateTime function
    
    // declare playSound and stopSound function
    
    func playSound(soundName: String) {
        let url = Bundle.main.url(forResource: soundName, withExtension: "mp3")
        self.player = try! AVAudioPlayer(contentsOf: url!)
        self.player.play()
        self.player.numberOfLoops = -1 // the sound should keep looping until the timer is stopped
        
    } // close playSound function
    
    //change format to 00:00 (min:sec)
    func timeString(time: TimeInterval) -> String {
        let minutes = Int(time) / 60 % 60
        let seconds = Int(time) % 60
        return String(format: "%02d:%02d", minutes, seconds) // return the time in the format of 00:00
    } // close timeString function
}

// The time was set to 3, 4, 7 for the testing purpose, to put the app to practical use, change back to below:
// let eggTime : [String : Int] = ["Soft": 300, "Medium" : 420, "Hard" : 720]
Previous
Previous

the making of quiz app

Next
Next

the making of a visually stunning xylophone app