SwiftUI中如何从GameView访问TimerComponent的timerRunning属性
解决SwiftUI中父视图控制子视图计时器状态的问题
要实现GameView点击按钮控制TimerComponent的timerRunning,核心是把计时器的状态从子视图提升到父视图或者使用共享视图模型——因为@State是视图内部的私有状态,外部无法直接访问。下面是两种简单可行的方案:
方案一:使用@Binding
把TimerComponent的状态改为@Binding,由父视图GameView提供状态源:
- 修改GameView,新增控制计时器的
@State变量,并传递给TimerComponent:
struct GameView: View { @State private var cardNumber = 0 @State var showGameView: Bool @State private var timerRunning = true // 新增:父视图持有计时器状态 let sequence = (0..<cardArray.count).shuffled() var body: some View { ZStack { BackgroundComponent() VStack { Spacer() // 传递绑定给子视图 TimerComponent(timerRunning: $timerRunning) Spacer() CardComponent(cardDifficulty: cardArray[sequence[cardNumber]].difficulty, cardTitle: cardArray[sequence[cardNumber]].title, cardNotes: cardArray[sequence[cardNumber]].notes) Spacer() Button { dealCard() timerRunning = true // 直接修改状态,子视图会同步更新 SoundManager.instance.player?.stop() } label: { PrimaryButton(text: "NEXT") } } } } // 补充dealCard方法(原代码中未实现) private func dealCard() { // 这里实现切换卡片的逻辑,比如cardNumber += 1等 } }
- 修改TimerComponent,将
@State改为@Binding:
struct TimerComponent: View { @State private var countdownTimer = 60 @Binding var timerRunning: Bool // 改为绑定类型 let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect() var body: some View { Text(timerRunning ? "\(countdownTimer)" : "Time's up!") .onReceive(timer){ _ in if countdownTimer > 0 && timerRunning { countdownTimer -= 1 if countdownTimer == 10 { SoundManager.instance.playSound(sound: .flatline) } } else if countdownTimer == 0 { timerRunning = false countdownTimer = -1 } } } }
方案二:使用ObservableObject(视图模型)
如果后续计时器逻辑更复杂,推荐用共享视图模型管理状态,实现更清晰的状态共享:
- 创建计时器视图模型:
class TimerViewModel: ObservableObject { @Published var countdownTimer = 60 @Published var timerRunning = true private var timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect() init() { startTimer() } private func startTimer() { _ = timer.sink { [weak self] _ in guard let self = self else { return } if self.countdownTimer > 0 && self.timerRunning { self.countdownTimer -= 1 if self.countdownTimer == 10 { SoundManager.instance.playSound(sound: .flatline) } } else if self.countdownTimer == 0 { self.timerRunning = false self.countdownTimer = -1 } } } // 重置计时器的方法,供父视图调用 func resetTimer() { countdownTimer = 60 timerRunning = true } }
- 修改GameView,引用视图模型:
struct GameView: View { @State private var cardNumber = 0 @State var showGameView: Bool @StateObject private var timerVM = TimerViewModel() // 初始化视图模型 let sequence = (0..<cardArray.count).shuffled() var body: some View { ZStack { BackgroundComponent() VStack { Spacer() TimerComponent(viewModel: timerVM) Spacer() CardComponent(cardDifficulty: cardArray[sequence[cardNumber]].difficulty, cardTitle: cardArray[sequence[cardNumber]].title, cardNotes: cardArray[sequence[cardNumber]].notes) Spacer() Button { dealCard() timerVM.resetTimer() // 调用视图模型的方法重置计时器 SoundManager.instance.player?.stop() } label: { PrimaryButton(text: "NEXT") } } } } private func dealCard() { // 卡片切换逻辑 } }
- 修改TimerComponent,依赖视图模型:
struct TimerComponent: View { @ObservedObject var viewModel: TimerViewModel // 观察视图模型 var body: some View { Text(viewModel.timerRunning ? "\(viewModel.countdownTimer)" : "Time's up!") } }
关键说明
@State仅用于视图内部的私有状态,外部无法直接修改;@Binding用于在父子视图间传递状态引用,实现双向同步;ObservableObject适合多视图共享状态的场景,逻辑更集中,便于维护。
内容的提问来源于stack exchange,提问作者TheDoctor
相关产品推荐
相关产品推荐

