我有一个视图模型,它有多个子视图模型。我对 watchOS、SwiftUI 和 Combine 还很陌生——借此机会学习。
我有一个 watchUI
- 播放按钮(视图) -
SetTimerPlayPauseButton
- 显示时间的文本(视图) -
TimerText
- 查看模型 - 具有 1
WatchDayProgramViewModel
- N:ExerciseTestClass
- N:SetInformationTestClass
。对于每个练习集,都有一个watchTimer & watchTimerSubscription
,我已经设法运行计时器来更新剩余的休息时间。 - ContentView - 已将 ViewModel 注入为
EnvironmentObject
如果我点击SetTimerPlayPauseButton
启动计时器,则计时器正在运行、工作并正确更改剩余RestTime(子视图模型中的属性SetInformationTestClass
),但更新/更改不会“发布”到TimerText
View。
我已经完成了其他 SO 答案中的大部分(如果不是全部)建议,我什至完成了我的所有WatchDayProgramViewModel
and ExerciseTestClass
, SetInformationTestClass
properties @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
}