the making of egg timer app
So how do you like you eggs?
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]