swiftswiftui

SwiftUI - How to display countdown timer


I want to display a countdown timer, which will take the current hour, minute, second, then count down to 00:00:00. Currently I have the current date displayed, But it's adding up.

My expectation: It will count down and when it gets back to 00:00:00 it will stop.

Below is all my code

struct TimerView: View {
    @State var timeRemaining = 24*60*60
    let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
    var body: some View {
        HStack(spacing: 12) {
            timerStringView(timer: "\(hourString(timer: timeRemaining))")
            timerStringView(timer: "\(minutesString(timer: timeRemaining))")
            timerStringView(timer: "\(secondsString(timer: timeRemaining))")
        }
        .frame(width: 300, height: 34)
        .background(Color.gray)
        .onReceive(timer) { _ in
            if timeRemaining > 0 {
                timeRemaining -= 1
            } else {
                timer.upstream.connect().cancel()
            }
        }
    }
    
    func timerStringView(timer: String) -> some View {
        Text(timer)
            .font(.system(size: 18, weight: .bold))
            .foregroundColor(.black)
            .background(Color.white)
            .cornerRadius(6)
    }
    
    func hourString(timer: Int) -> String {
        let date = Date()
        let calendar = Calendar.current
        let hours = calendar.component(.hour, from: date)
        return String(format: "%02i", hours)
    }
    
    func minutesString(timer: Int) -> String {
        let date = Date()
        let calendar = Calendar.current
        let minutes = calendar.component(.minute, from: date)
        return String(format: "%02i", minutes)
    }
    
    func secondsString(timer: Int) -> String {
        let date = Date()
        let calendar = Calendar.current
        let second = calendar.component(.second, from: date)
        return String(format: "%02i", second)
    }
}

Solution

  • Using on appear get the hours minutes and seconds all added up into seconds to set the @State. Note: taking the hours and multiplying by 3600 was bugging out, so I used an extra variable to fix it on my machine.

    struct ContentView: View {
        @State var timeRemaining:Int = 0
        let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
        var body: some View {
            HStack(spacing: 12) {
                timerStringView(timer: "\(hourString(timer: timeRemaining))")
                timerStringView(timer: "\(minutesString(timer: timeRemaining))")
                timerStringView(timer: "\(secondsString(timer: timeRemaining))")
            }
            .frame(width: 300, height: 34)
            .background(Color.gray)
            .onReceive(timer) { _ in
                if timeRemaining > 0 {
                    timeRemaining -= 1
                } else {
                    timer.upstream.connect().cancel()
                }
            }
            // Get the time
            .onAppear() {
                setTimer()
            }
        }
        
        func timerStringView(timer: String) -> some View {
            Text(timer)
                .font(.system(size: 18, weight: .bold))
                .foregroundColor(.black)
                .background(Color.white)
                .cornerRadius(6)
        }
        func hourString(timer: Int) -> String {
            let date = Date()
            let calendar = Calendar.current
            let hours = calendar.component(.hour, from: date)
            return String(format: "%02i", hours)
        }
        
        func minutesString(timer: Int) -> String {
            let date = Date()
            let calendar = Calendar.current
            let minutes = calendar.component(.minute, from: date)
            return String(format: "%02i", minutes)
        }
        
        func secondsString(timer: Int) -> String {
            let date = Date()
            let calendar = Calendar.current
            let second = calendar.component(.second, from: date)
            return String(format: "%02i", second)
        }
        func setTimer() {
            let date = Date()
            let calendar = Calendar.current
            let hours = calendar.component(.hour, from: date)
            let hourSeconds = hours * 3600
            let minuteSeconds = calendar.component(.minute, from: date) * 60
            let seconds = calendar.component(.second, from: date)
            timeRemaining = hourSeconds
            timeRemaining += minuteSeconds
            timeRemaining += seconds
        }
    }