0

我有一个视图模型,它有多个子视图模型。我对 watchOS、SwiftUI 和 Combine 还很陌生——借此机会学习。

我有一个 watchUI

  1. 播放按钮(视图) -SetTimerPlayPauseButton
  2. 显示时间的文本(视图) -TimerText
  3. 查看模型 - 具有 1 WatchDayProgramViewModel- N: ExerciseTestClass- N: SetInformationTestClass。对于每个练习集,都有一个watchTimer & watchTimerSubscription,我已经设法运行计时器来更新剩余的休息时间。
  4. ContentView - 已将 ViewModel 注入为EnvironmentObject

如果我点击SetTimerPlayPauseButton启动计时器,则计时器正在运行、工作并正确更改剩余RestTime(子视图模型中的属性SetInformationTestClass),但更新/更改不会“发布”到TimerTextView

我已经完成了其他 SO 答案中的大部分(如果不是全部)建议,我什至完成了我的所有WatchDayProgramViewModeland ExerciseTestClass, SetInformationTestClassproperties @Published,但是当视图模型属性更新时,它们仍然没有更新 View,如下面的 Xcode 调试器所示。

在此处输入图像描述

请查看我的代码,并就如何改进它给我一些建议。

内容视图

struct ContentView: View {
    @State var selectedTab = 0
    @StateObject var watchDayProgramVM = WatchDayProgramViewModel()
    
    var body: some View {
        
        TabView(selection: $selectedTab) {
            
            SetRestDetailView().id(2)
    
        }
        .environmentObject(watchDayProgramVM)
        .tabViewStyle(PageTabViewStyle())
        .indexViewStyle(.page(backgroundDisplayMode: .automatic))
        
    }
}

    
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        Group {
            ContentView(watchDayProgramVM: WatchDayProgramViewModel())
        }
    }
}

设置RestDetailView

import Foundation
import SwiftUI
import Combine

struct SetRestDetailView: View {
    
    @EnvironmentObject var watchDayProgramVM: WatchDayProgramViewModel
    
    var setCurrentHeartRate: Int = 120
    @State var showingLog = false
    
    
    var body: some View {


                    HStack {
         
                        let elapsedRestTime = watchDayProgramVM.exerciseVMList[0].sets[2].elapsedRestTime
                        let totalRestTime = watchDayProgramVM.exerciseVMList[0].sets[2].totalRestTime
                        
                        TimerText(elapsedRestTime: elapsedRestTime, totalRestTime: totalRestTime, rect: rect)
                            .border(Color.yellow)

                    }
                    
                    HStack {
                        
                        SetTimerPlayPauseButton(isSetTimerRunningFlag: false,
                                                playImage: "play.fill",
                                                pauseImage: "pause.fill",
                                                bgColor: Color.clear,
                                                fgColor: Color.white.opacity(0.5),
                                                rect: rect) {
                            
                            print("playtimer button tapped")
                            self.watchDayProgramVM.exerciseVMList[0].sets[2].startTimer()
                            
                            
                            let elapsedRestTime = watchDayProgramVM.exerciseVMList[0].sets[2].elapsedRestTime
                            let totalRestTime = watchDayProgramVM.exerciseVMList[0].sets[2].totalRestTime
                            print("printing elapsedRestTime from SetRestDetailView \(elapsedRestTime)")
                            print("printing elapsedRestTime from SetRestDetailView \(totalRestTime)")
                            
                        }
                            .border(Color.yellow)

                    }

 }

}

定时器文本

struct TimerText: View {
    var elapsedRestTime: Int
    var totalRestTime: Int
    var rect: CGRect
    
    var body: some View {
        VStack {
            Text(counterToMinutes())
                .font(.system(size: 100, weight: .semibold, design: .rounded))
                .kerning(0)
                .fontWeight(.semibold)
                .minimumScaleFactor(0.25)
                .padding(-1)
        }
    }
    
    func counterToMinutes() -> String {
        let currentTime = totalRestTime - elapsedRestTime
        let seconds = currentTime % 60
        let minutes = Int(currentTime / 60)
        
        if currentTime > 0 {
            return String(format: "%02d:%02d", minutes, seconds)
        }
        
        else {
            return ""
        }
    }
}

视图模型

import Combine

final class WatchDayProgramViewModel: ObservableObject {
    
    @Published var exerciseVMList: [ExerciseTestClass] = [
 (static/hard-coded values for testing)
]

class ExerciseTestClass: ObservableObject {
    
    init(exercise: String, sets: [SetInformationTestClass]) {
        
        self.exercise = exercise
        self.sets = sets
        
    }
    
        var exercise: String
        @Published var sets: [SetInformationTestClass]
    
    }

class SetInformationTestClass: ObservableObject {
    
    init(totalRestTime: Int, elapsedRestTime: Int, remainingRestTime: Int, isTimerRunning: Bool) {
        
        self.totalRestTime = totalRestTime
        self.elapsedRestTime = elapsedRestTime
        self.remainingRestTime = remainingRestTime
        self.isTimerRunning = isTimerRunning
        
    }
    
    @Published var totalRestTime: Int
    @Published var elapsedRestTime: Int
    @Published var remainingRestTime: Int
    
    @Published var isTimerRunning = false
    @Published var watchTimer = Timer.publish(every: 1.0, on: .main, in: .default)
    @Published var watchTimerSubscription: AnyCancellable? = nil
    
    @Published private var startTime: Date? = nil
    
    
    func startTimer() {
        
        print("startTimer initiated")
        self.watchTimerSubscription?.cancel()
        
        if startTime == nil {
            startTime = Date()
        }
        
        self.isTimerRunning = true
        
        self.watchTimerSubscription = watchTimer
            .autoconnect()
            .sink(receiveValue: { [weak self] _ in
                
                guard let self = self, let startTime = self.startTime else { return }
                
                let now = Date()
                let elapsedTime = now.timeIntervalSince(startTime)
                
                self.remainingRestTime = self.totalRestTime - Int(elapsedTime)
                
                self.elapsedRestTime = self.totalRestTime - self.remainingRestTime
                                                    
                guard self.remainingRestTime > 0 else {
                        self.pauseTimer()
                        return
                    }

self.objectWillChange.send()
                
                print("printing elapsedRest Time \(self.elapsedRestTime) sec")
                print("printing remaining Rest time\(self.remainingRestTime)sec ")
                
            })
    }
    
    func pauseTimer() {
        //stop timer and retain elapsed rest time
        
        print("pauseTimer initiated")
        self.watchTimerSubscription?.cancel()
        self.watchTimerSubscription = nil
        self.isTimerRunning = false
        self.startTime = nil
        
    }
    
        
4

1 回答 1

0

在@lorem ipsum 和他的反馈的帮助下设法解决了这个问题。根据他的评论,问题在于

它很可能不起作用,因为ObservableObjects @Published当变量更改时,您正在链接只会在对象整体更改时检测到更改。一种测试方法是通过使用将对象作为参数的子视图将每个包装SetInformationTestClass在一个中。@ObservbleObject

之后,我设法找到了关于嵌套视图模型(尤其是子视图)更改的类似 SO 答案,并将子视图模型设为 ObservedObject。子视图模型中的更改已填充到视图中。请参阅下面的更改代码。

设置RestDetailView

import Foundation
import SwiftUI
import Combine

struct SetRestDetailView: View {
    
    @EnvironmentObject var watchDayProgramVM: WatchDayProgramViewModel
    
    var setCurrentHeartRate: Int = 120
    @State var showingLog = false
    
    
    var body: some View {


                    HStack {
         
                        let elapsedRestTime = watchDayProgramVM.exerciseVMList[0].sets[2].elapsedRestTime
                        let totalRestTime = watchDayProgramVM.exerciseVMList[0].sets[2].totalRestTime
                        
                        let setInformatationVM = self.watchDayProgramVM.exerciseVMList[0].sets[2]
                        
                        TimerText(setInformationVM: setInformatationVM, rect: rect)
                            .border(Color.yellow)

                    }
                    
                    HStack {
                        
                        SetTimerPlayPauseButton(isSetTimerRunningFlag: false,
                                                playImage: "play.fill",
                                                pauseImage: "pause.fill",
                                                bgColor: Color.clear,
                                                fgColor: Color.white.opacity(0.5),
                                                rect: rect) {
                            
                            print("playtimer button tapped")
                            self.watchDayProgramVM.exerciseVMList[0].sets[2].startTimer()
                            
                            
                            let elapsedRestTime = watchDayProgramVM.exerciseVMList[0].sets[2].elapsedRestTime
                            let totalRestTime = watchDayProgramVM.exerciseVMList[0].sets[2].totalRestTime
                            print("printing elapsedRestTime from SetRestDetailView \(elapsedRestTime)")
                            print("printing elapsedRestTime from SetRestDetailView \(totalRestTime)")
                            
                        }
                            .border(Color.yellow)

                    }

 }

}

定时器文本

struct TimerText: View {
    
    @ObservedObject var setInformationVM: SetInformationTestClass
    
//    @State var elapsedRestTime: Int
//    @State var totalRestTime: Int
    var rect: CGRect
    
    var body: some View {
        VStack {
            Text(counterToMinutes())
                .font(.system(size: 100, weight: .semibold, design: .rounded))
                .kerning(0)
                .fontWeight(.semibold)
                .minimumScaleFactor(0.25)
                .padding(-1)
        }
    }
    
    func counterToMinutes() -> String {
        let currentTime = setInformationVM.totalRestTime - setInformationVM.elapsedRestTime
        let seconds = currentTime % 60
        let minutes = Int(currentTime / 60)
        
        if currentTime > 0 {
            return String(format: "%02d:%02d", minutes, seconds)
        }
        
        else {
            return ""
        }
    }
}
于 2022-01-21T16:23:11.683 回答