我正在尝试使用 Swift 编写一个小型间隔计时器应用程序,但可能无法更新我的视图。 仅当一个间隔结束时视图才会更新,因此计时器变为 0,新的间隔以新时间开始。
我认为由于 ObservableObjects 和 @Published 声明,我不必做太多事情,但似乎我在关于视图和模型之间的 swift 和交互的逻辑中存在一些关键错误?
我的 RunningTimerView 中嵌入了一个用于计时器的 TimerClockView。用于逻辑内容和连接的 IntervalTimerViewModel,尽管我认为它做得不够好并且有改进的空间。然后是我的 MyTimer 类用于定时器的东西。
到目前为止,我尝试使用不同的计时器,timer.scheduledTimer 和timer.publish 来看看这是否有任何区别。我尝试将新值保存在整个视图模型和 myTimer 中的不同变量中,并尝试在计算时间时使用侦听器回调,就像一个间隔完成时(currentProcessingDuration = 0)一样。我尝试在不同的空间引入计时器,因为我想也许我调用了另一个对象,但看起来并非如此。 在初始化程序中传递视图模型以将其添加到 EnvironmentObject 和使用 @EnvironmentObject 而不是 @ObservableObject 之间进行切换。在调用 navigationdestination 时也将其与视图一起传递 ChatGPT-4 说我的代码很好,ObservableObject/ObservedObject/@Published 应该像预期的那样工作......所以这可能是另一个与给定代码无关的问题?或者 get 是愚蠢的,有时是这样 为了便于阅读,我缩短了课程..
定时器时钟视图
struct TimerClockView: View {
@ObservedObject var viewModel: IntervalTimerViewModel
// some code
var body: some View {
ZStack {
// some code
if viewModel.isPlaying {
Text("\(viewModel.myTimer.currentProcessingDurationString)")
.font(.system(size: customSize * 0.25))
Text("\(viewModel.myTimer.getTotalInMinutes())")
.offset(y: customSize * 0.2)
.font(.system(size: customSize * 0.15))
} else {
Text("\(viewModel.myTimer.getTotalInMinutes())")
.font(.system(size: customSize * 0.25))
}
}
.foregroundStyle(currentColor)
}
}
运行定时器视图
struct RunningTimerView: View {
@ObservedObject var viewModel: IntervalTimerViewModel
// some code
var body: some View {
ZStack {
VStack {
// some code
TimerClockView(viewModel: viewModel,customSize: UIScreen.main.bounds.width * 0.8)
// some code
}
.background(currentColor)
}
}
}
IntervalTimerViewModel
class IntervalTimerViewModel: ObservableObject {
@Published var settings: Settings
@Published var intervalProfile: IntervalProfile
@Published var myTimer: MyTimer
@Published var currentInterval: Interval?
@Published var currentExercise: Exercise?
@Published var volumeLevel: Float = 50
@Published var intervalMode: IntervalMode?
@Published var currentActivityType: ActivityType = .none
@Published var isPlaying: Bool = false
var currentRound: Int = 1
var totalRounds: Int = 0
var totalExercises: Int = 0
var currentExerciseIndex: Int = 0
init(intervalProfile: IntervalProfile, settings: Settings) {
self.settings = settings
self.intervalProfile = intervalProfile
self.myTimer = MyTimer()
guard let interval = intervalProfile.intervals.first else { return }
self.currentInterval = interval
self.currentExercise = interval.exercises.first
self.totalRounds = interval.roundCounter
self.totalExercises = interval.exerciseCounter
self.intervalMode = interval.intervalMode
setCurrentTimer(interval: interval)
setTimerListener()
}
func skipToNextExercise() -> Bool {
// some code
}
func setCurrentTimer(interval: Interval?) {
if let interval = interval {
myTimer.setTimes(exercises: interval.exerciseCounter, repetition: interval.roundCounter, activityDuration: interval.activityDuration, pauseDuration : interval.pauseDuration, variableDuration: interval.variableDuration, currentProcessingDuration: interval.activityDuration, warmUpTime: settings.defaultWarmUpTime)
}
}
func setTimerListener() {
myTimer.onExercisePauseTimerEnd = { [weak self] in
self?.setNextActivity()}
}
// for when one interval ends and the next starts
func setNextActivity() {
guard let interval = currentInterval else { return}
print("\(currentActivityType)")
switch currentActivityType {
case .none:
myTimer.currentProcessingDuration = settings.defaultWarmUpTime
currentActivityType = .warmUp
break
case .activity:
if currentExerciseIndex + 1 < interval.exercises.count {
currentExerciseIndex += 1
currentExercise = interval.exercises[currentExerciseIndex]
myTimer.currentProcessingDuration = interval.pauseDuration
currentActivityType = .pause
} else {
if currentRound < totalRounds {
currentRound += 1
currentExerciseIndex = 0
currentActivityType = .roundPause
myTimer.currentProcessingDuration = interval.variableDuration
} else {
stopTimer()
return
}
}
break
case .pause:
currentExercise = interval.exercises[currentExerciseIndex]
myTimer.currentProcessingDuration = interval.activityDuration
currentActivityType = .activity
break
case .roundPause:
currentExerciseIndex = 0
currentExercise = interval.exercises[currentExerciseIndex]
myTimer.currentProcessingDuration = interval.activityDuration
currentActivityType = .activity
break
case .warmUp:
currentExerciseIndex = 0
currentExercise = interval.exercises[currentExerciseIndex]
myTimer.currentProcessingDuration = interval.activityDuration
currentActivityType = .activity
break
case .coolDown:
print("")
// not yet implemented
// Method could be used to define cooldown after sesion for stretching or whatever
break
}
startTimer()
}
func startTimer() {
if !isPlaying {
changeVolume()
myTimer.startTimer()
isPlaying = true
}
}
func pauseTimer() {
if isPlaying {
changeVolume()
myTimer.pauseTimer()
isPlaying = false
}
}
func continueTimer() {
if !isPlaying {
changeVolume()
isPlaying = true
myTimer.continueTimer()
}
}
func stopTimer() {
isPlaying = false
changeVolume()
myTimer.stopTimer()
resetActivities()
}
// some code
}
我的计时器
class MyTimer: ObservableObject {
@Published var totalDuration: Int = 0
@Published var currentProcessingDuration: Int = 0
@Published var totalDurationString: String = ""
@Published var currentProcessingDurationString: String = ""
@Published var audioPlayer: AVAudioPlayer?
private var lastCurrentProcessingDuration: Int = 0
var onExercisePauseTimerEnd: (() -> Void)?
@Published var timer: Timer?
init() {
audioPlayer = AVAudioPlayer()
audioPlayer?.volume = loadAudioVolume()
}
func setTimes(exercises: Int = 0, repetition: Int = 0, activityDuration: Int = 0, pauseDuration : Int = 0, variableDuration: Int = 0, currentProcessingDuration: Int = 0, warmUpTime: Int = 0) {
let totalActivity = exercises * activityDuration * repetition
let activityPause = exercises * pauseDuration * repetition
let roundPause = (repetition - 1) * variableDuration
self.totalDuration = totalActivity + activityPause + roundPause + warmUpTime
self.currentProcessingDuration = currentProcessingDuration
self.totalDurationString = getTotalInMinutes()
self.currentProcessingDurationString = getCurrentProcessingInMinutes()
}
func startTimer() {
configureAudioPlayer(audioName: "startIntervalSound")
timer?.invalidate()
timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) {
[weak self] _ in DispatchQueue.main.async {
self?.tick()
}
}
}
func tick() {
print("currentProcessingDuration: \(currentProcessingDuration)")
print("totalDuration: \(totalDuration)")
if currentProcessingDuration == lastCurrentProcessingDuration {
configureAudioPlayer(audioName: "beepStartSound")
}else if currentProcessingDuration <= 4 && currentProcessingDuration > 0 {
configureAudioPlayer(audioName: "beepSound")
}
if currentProcessingDuration == 0 {
configureAudioPlayer(audioName: "beepEndSound")
onExercisePauseTimerEnd?()
}
if currentProcessingDuration > 0 && totalDuration > 0 {
currentProcessingDuration -= 1
totalDuration -= 1
}
if(totalDuration == 0) {
stopTimer()
return
}
}
func pauseTimer() {
configureAudioPlayer(audioName: "pauseIntervalSound")
timer?.invalidate()
}
func continueTimer() {
configureAudioPlayer(audioName: "continueIntervalSound")
lastCurrentProcessingDuration = currentProcessingDuration
startTimer()
}
func stopTimer() {
configureAudioPlayer(audioName: "stopIntervalSound")
timer?.invalidate()
timer = nil
AudioServicesPlaySystemSound(1100)
}
func getTotalInMinutes() -> String {
return getTimeInMinutes(processedTime: totalDuration)
}
func getCurrentProcessingInMinutes() -> String {
return getTimeInMinutes(processedTime: currentProcessingDuration)
}
func getTimeInMinutes(processedTime: Int) -> String {
return String(format: "%d:%02d", processedTime / 60, processedTime%60)
}
// some code
}
有人可以给我一些解决我的问题的提示吗?谢谢!
感谢 Iorem ipsum 提供提示。
我现在将 MyTimer 作为参数传递给 TimerClockView 并将其写入 ObservedObject。就像魅力一样工作
struct TimerClockView: View {
@ObservedObject var viewModel: IntervalTimerViewModel
@ObservedObject var myTimer: MyTimer
// some code
var body: some View {
ZStack {
// some code
if viewModel.isPlaying {
Text(myTimer.currentProcessingDurationString)
.font(.system(size: customSize * 0.25))
Text(myTimer.totalDurationString)
.offset(y: customSize * 0.2)
.font(.system(size: customSize * 0.15))
} else {
Text(myTimer.totalDurationString)
.font(.system(size: customSize * 0.25))
}
}
.foregroundStyle(currentColor)
}
}
struct RunningTimerView: View {
@ObservedObject var viewModel: IntervalTimerViewModel
// some code
TimerClockView(viewModel: viewModel, myTimer: viewModel.myTimer, customSize: UIScreen.main.bounds.width * 0.8)
// some code
}