Swift 嵌套 ObservableObject 不更新计时器视图

问题描述 投票:0回答:1

我正在尝试使用 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    
}

有人可以给我一些解决我的问题的提示吗?谢谢!

swift swiftui
1个回答
0
投票

感谢 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

}

© www.soinside.com 2019 - 2024. All rights reserved.