You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI中变量变更为何一个View更新另一个不更新?

问题分析与解决方案

核心问题原因

你的代码存在两个关键问题,导致窗口内的文本标签不更新:

1. 状态修改未在主线程执行

Timer.scheduledTimer的回调默认运行在当前RunLoop的线程,虽然通常是主线程,但存在RunLoop模式不匹配的可能(比如窗口失去焦点时),导致修改@State变量的操作无法触发SwiftUI的视图更新。SwiftUI要求所有状态变更必须在主线程执行才能正确刷新视图。

2. 两个独立的ContentView实例

你在AppDelegate中创建了一个全新的ContentView()实例用于菜单栏,而窗口中的是另一个完全独立的实例。两者的@State变量是各自隔离的——点击窗口按钮修改的是窗口实例的timeGone,不会影响菜单栏的实例。你提到菜单栏标签能更新,大概率是测试场景的偏差,本质上两个实例的状态完全无关。


分步解决方案

步骤1:修复主线程状态更新问题

修改startTimer方法,强制在主线程更新状态变量:

func startTimer(){
    timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: {_ in
        DispatchQueue.main.async {
            self.timeGone += 1
        }
    })
}

步骤2:实现跨视图状态共享

如果需要窗口和菜单栏共享同一个计时状态,必须使用ObservableObject创建共享状态模型,替代仅适用于单个视图的@State:

1. 创建共享计时器模型

import SwiftUI
import Combine

class TimerModel: ObservableObject {
    @Published var timeGone = 0
    private var timer: Timer?
    
    func startTimer() {
        timer?.invalidate() // 避免重复创建计时器
        timeGone = 0 // 重置计时
        timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] _ in
            DispatchQueue.main.async {
                self?.timeGone += 1
            }
        }
    }
    
    func stopTimer() {
        timer?.invalidate()
        timer = nil
    }
}

2. 修改ContentView使用共享模型

将@State替换为@ObservedObject,接收外部传递的共享模型:

struct ContentView: View {
    @ObservedObject var timerModel: TimerModel
    
    var MenuBar: some View {
        VStack{
            Text(String(timerModel.timeGone))
                .font(.system(size: 12))
                .foregroundColor(.white)
                .padding(.vertical, 1)
                .padding(.horizontal, 4)
                .overlay(
                    RoundedRectangle(cornerRadius: 5.0)
                        .stroke(Color.white, lineWidth: 1.2)
                )
        }
        .padding(.vertical, 2)
        .padding(.horizontal, 2)
        .background(Color.clear)
    }
    
    var body: some View {
        VStack {
            Text(String(timerModel.timeGone))
                .fontWeight(.semibold)
                .font(.system(size: 90))
            
            Spacer().frame(height: 30)
            
            Button("Start") {
                timerModel.startTimer()
            }
            .buttonStyle(StartButtonStyle())
        }
        .fixedSize()
        .frame(width: 340.0, height: 200.0)
        .padding(.all, 50.0)
    }
}

3. 修改AppDelegate传递共享模型

创建全局共享的模型实例,同时传递给窗口和菜单栏的ContentView:

class AppDelegate: NSObject, NSApplicationDelegate {
    var statusItem: NSStatusItem!
    private let timerModel = TimerModel() // 共享的模型实例
    
    private lazy var contentView: NSView? = {
        guard let window = statusItem.value(forKey: "window") as? NSWindow else {
            return nil
        }
        return window.contentView
    }()

    func applicationDidFinishLaunching(_ notification: Notification) {
        setupMenuBar()
        // 创建并显示主窗口,传递共享模型
        let hostingController = NSHostingController(rootView: ContentView(timerModel: timerModel))
        let window = NSWindow(
            contentRect: NSRect(x: 0, y: 0, width: 440, height: 300),
            styleMask: [.titled, .closable, .miniaturizable, .resizable],
            backing: .buffered, defer: false
        )
        window.contentViewController = hostingController
        window.center()
        window.makeKeyAndOrderFront(nil)
    }
    
    func setupMenuBar(){
        statusItem = NSStatusBar.system.statusItem(withLength: 45)
    
        guard let contentView = self.contentView,
              let menuButton = statusItem.button
        else {return}
        
        let hostingView = NSHostingView(rootView: ContentView(timerModel: timerModel).MenuBar)
        hostingView.translatesAutoresizingMaskIntoConstraints = false
        contentView.addSubview(hostingView)
        
        NSLayoutConstraint.activate([
            hostingView.topAnchor.constraint(equalTo: contentView.topAnchor),
            hostingView.rightAnchor.constraint(equalTo: contentView.rightAnchor),
            hostingView.leftAnchor.constraint(equalTo: contentView.leftAnchor),
            hostingView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor)
        ])
    }
}

额外注意事项

  • @State仅适用于单个视图内部的状态管理,跨视图共享状态必须使用ObservableObject配合@ObservedObject/@StateObject。
  • 计时器使用完毕后要调用invalidate()避免内存泄漏,比如视图消失时或重新启动计时器前。
  • 所有修改SwiftUI状态的操作必须确保在主线程执行,否则无法触发视图更新。

内容的提问来源于stack exchange,提问作者Edi Bociu

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.16 01:39:57