Я пытаюсь сделать таймер в swiftui, но всякий раз, когда я его запускаю, он автоматически добавляет 30 минут к обратному отсчету. Например, когда я устанавливаю время обратного отсчета на 5 минут и нажимаю кнопку «Пуск», вместо этого оно будет отображаться как 35 минут, но когда я снова нажимаю кнопку, оно просто продолжает переключаться на случайное время. Выше указано случайное время, на которое он переключится.
Я получил этот таймер из учебника на YouTube от Indently, но изменил некоторые вещи, чтобы они соответствовали тому, что я хотел. Я попытался установить пользовательское время, чтобы таймер всегда отсчитывал от 5 минут. Насколько я понимаю, таймер работает, беря разницу между текущей датой и датой окончания, а затем используя разницу во времени в качестве обратного отсчета. Ниже приведен код для TimerStruct (ViewModel) и TimerView.
ТаймерСтруктура:
import Foundation
extension TimerView {
final class ViewModel: ObservableObject {
@Published var isActive = false
@Published var showingAlert = false
@Published var time: String = "5:00"
@Published var minutes: Float = 5.0 {
didSet {
self.time = "\(Int(minutes)):00"
}
}
var initialTime = 0
var endDate = Date()
// Start the timer with the given amount of minutes
func start(minutes: Float) {
self.initialTime = 5
self.endDate = Date()
self.isActive = true
self.endDate = Calendar.current.date(byAdding: .minute, value: Int(minutes), to: endDate)!
}
// Reset the timer
func reset() {
self.minutes = Float(initialTime)
self.isActive = false
self.time = "\(Int(minutes)):00"
}
// Show updates of the timer
func updateCountdown(){
guard isActive else { return }
// Gets the current date and makes the time difference calculation
let now = Date()
let diff = endDate.timeIntervalSince1970 - now.timeIntervalSince1970
// Checks that the countdown is not <= 0
if diff <= 0 {
self.isActive = false
self.time = "0:00"
self.showingAlert = true
return
}
// Turns the time difference calculation into sensible data and formats it
let date = Date(timeIntervalSince1970: diff)
let calendar = Calendar.current
let minutes = calendar.component(.minute, from: date)
let seconds = calendar.component(.second, from: date)
// Updates the time string with the formatted time
self.minutes = Float(minutes)
self.time = String(format:"%d:%02d", minutes, seconds)
}
}
}
ТаймерВид:
import SwiftUI
struct TimerView: View {
@ObservedObject var vm = ViewModel()
let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
let width: Double = 250
var body: some View {
VStack {
Text("Timer: \(vm.time)")
.font(.system(size: 50, weight: .medium, design: .rounded))
.alert("Timer done!", isPresented: $vm.showingAlert) {
Button("Continue", role: .cancel) {
}
}
.padding()
HStack(spacing:50) {
Button("Start") {
vm.start(minutes: Float(vm.minutes))
}
.padding()
.background((Color(red: 184/255, green: 243/255, blue: 255/255)))
.foregroundColor(.black)
.cornerRadius(10)
.font(Font.system(size: UIFontMetrics.default.scaledValue(for: 16)))
//.disabled(vm.isActive)
if vm.isActive == true {
Button("Pause") {
vm.isActive = false
//self.timer.upstream.connect().cancel()
}
.padding()
.foregroundColor(.black)
.background(.red)
.cornerRadius(10)
.font(Font.system(size: UIFontMetrics.default.scaledValue(for: 16)))
} else {
Button("Resume") {
vm.isActive = true
}
.padding()
.foregroundColor(.black)
.background(.green)
.cornerRadius(10)
.font(Font.system(size: UIFontMetrics.default.scaledValue(for: 16)))
}
}
.frame(width: width)
}
.onReceive(timer) { _ in
vm.updateCountdown()
}
}
}
struct TimerView_Previews: PreviewProvider {
static var previews: some View {
TimerView()
}
}
Я заметил и исправил ряд вещей в вашем коде:
start()
вызывается с текущим значением vm.minutes
, поэтому он будет начинаться с этого значения, а не 5
. Я изменил его на использование self.initialTime
, что означает, что в настоящее время он не использует переданное значение. Вам нужно решить, действительно ли start()
хочет принять значение и как его использовать.
reset()
не звонили. Я называю это от start()
.
Пауза только приостанавливала обновление экрана. Я изменил его, чтобы отслеживать время начала паузы и вычислять время паузы, чтобы он мог точно обновлять отображаемое время.
Я сделал кнопку «Пауза/Возобновление» одной кнопкой с условными значениями заголовка и цвета на основе vm.active
.
Вот обновленный код:
extension TimerView {
final class ViewModel: ObservableObject {
@Published var isActive = false
@Published var showingAlert = false
@Published var time: String = "5:00"
@Published var minutes: Float = 5.0 {
didSet {
self.time = "\(Int(minutes)):00"
}
}
var initialTime = 0
var endDate = Date()
var pauseDate = Date()
var pauseInterval = 0.0
// Start the timer with the given amount of minutes
func start(minutes: Float) {
self.initialTime = 5
self.reset()
self.endDate = Date()
self.endDate = Calendar.current.date(byAdding: .minute, value: self.initialTime, to: endDate)!
self.isActive = true
}
// Reset the timer
func reset() {
self.isActive = false
self.pauseInterval = 0.0
self.minutes = Float(initialTime)
self.time = "\(Int(minutes)):00"
}
func pause() {
if self.isActive {
pauseDate = Date()
} else {
// keep track of the total time we're paused
pauseInterval += Date().timeIntervalSince(pauseDate)
}
self.isActive.toggle()
}
// Show updates of the timer
func updateCountdown(){
guard isActive else { return }
// Gets the current date and makes the time difference calculation
let now = Date()
let diff = endDate.timeIntervalSince1970 + self.pauseInterval - now.timeIntervalSince1970
// Checks that the countdown is not <= 0
if diff <= 0 {
self.isActive = false
self.time = "0:00"
self.showingAlert = true
return
}
// Turns the time difference calculation into sensible data and formats it
let date = Date(timeIntervalSince1970: diff)
let calendar = Calendar.current
let minutes = calendar.component(.minute, from: date)
let seconds = calendar.component(.second, from: date)
// Updates the time string with the formatted time
//self.minutes = Float(minutes)
self.time = String(format:"%d:%02d", minutes, seconds)
}
}
}
struct TimerView: View {
@ObservedObject var vm = ViewModel()
let timer = Timer.publish(every: 0.1, on: .main, in: .common).autoconnect()
let width: Double = 250
var body: some View {
VStack {
Text("Timer: \(vm.time)")
.font(.system(size: 50, weight: .medium, design: .rounded))
.alert("Timer done!", isPresented: $vm.showingAlert) {
Button("Continue", role: .cancel) {
}
}
.padding()
HStack(spacing:50) {
Button("Start") {
vm.start(minutes: Float(vm.minutes))
}
.padding()
.background((Color(red: 184/255, green: 243/255, blue: 255/255)))
.foregroundColor(.black)
.cornerRadius(10)
.font(Font.system(size: UIFontMetrics.default.scaledValue(for: 16)))
//.disabled(vm.isActive)
Button(vm.isActive ? "Pause" : "Resume") {
vm.pause()
//vm.isActive = false
//self.timer.upstream.connect().cancel()
}
.padding()
.foregroundColor(.black)
.background(vm.isActive ? .red : .green)
.cornerRadius(10)
.font(Font.system(size: UIFontMetrics.default.scaledValue(for: 16)))
}
.frame(width: width)
}
.onReceive(timer) { _ in
vm.updateCountdown()
}
}
}
Спасибо! Вам удалось заставить мой код работать намного лучше, но я все же заметил, что таймер по-прежнему добавляет дополнительные 30 минут, когда я запускаю его, хотя время обратного отсчета больше не прыгает как сумасшедшее.
Я не вижу дополнительных 30 минут с кодом, который я разместил.
ах, я вижу, я пытался запустить его на своем телефоне, и я все еще вижу проблему. Может быть, я могу отправить вам видео проблемы?
Выложите видео куда-нибудь, а сюда дайте ссылку.
Вот ссылка на видео: drive.google.com/drive/folders/…
Пожалуйста, сделайте доступ общедоступным.
О, извините за это. Теперь это общедоступно.
Создайте новый проект и используйте код, который я разместил. Можете ли вы воспроизвести проблему с этим кодом? Если нет, то в вашем коде что-то другое.
Я только что заметил, что если вы нажмете «Возобновить» после первого запуска приложения, вы получите странный результат. Это связано с тем, что код предполагает, что таймер приостановлен, но
endDate
еще не установлен должным образом. Вы можете исправить это, установивvar endDate = Calendar.current.date(byAdding: .minute, value: 5, to: Date())!
, или вы можете отключить кнопку «Возобновить», если «Пуск» никогда не нажимался.