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

Swift变量变更时如何自动更新View?实战场景咨询

问题:SwiftUI视图无法在数据变更时自动执行状态逻辑

问题背景

我有一个Flutter应用会定期发送带数据的消息,在SwiftUI的ViewModel中已实现数据解码逻辑,ContentView里用@State声明ViewModel,通过NavigationLink把viewModel的变量传给ChallengeTabView。目前收到新消息时视图能同步更新currentLives这类值,但根据StateType枚举编写的switch逻辑,放在.onAppear或.task里只有重新进入页面才触发,希望让它每次currentChallenge变更时自动执行,同时想明确数据模型用类还是结构体更合适。


相关代码

ChallengeTabView 代码

struct ChallengeTabView: View {
    let currentChallenge: CoreChallengeData
    @State private var timer: Timer?
    @State private var counter = 0
    @State private var isRunning = false
    @State private var showCancelAlert = false
    private var editableButtons: Bool {
        return currentChallenge.state.type != .DelayedChallengeState || currentChallenge.state.type != .NoActiveChallengeState
    }
    private var challengeIsActive: Bool {
        currentChallenge.state.type != .NoActiveChallengeState
    }
    @State private var message = "No Message"
    @State private var conter = 0 // 注:存在拼写错误,应为counter
    
    
    var body: some View {
        NavigationStack{
            Text(message)
            TabView{
                if challengeIsActive{
                    VStack(alignment: .leading){
                        Text("\(DateUtilities.formatSecondsToString(duration: currentChallenge.state.data.duration - counter))")
                            .font(.largeTitle)
                        HStack{
                            ForEach(0..<currentChallenge.state.data.maxLives, id: \.self){ life in
                                if life > currentChallenge.state.data.currentLives - 1{
                                    Image(systemName: "heart.slash")
                                        .resizable()
                                        .scaledToFit()
                                        .foregroundStyle(.tertiary)
                                } else{
                                    Image(systemName: "heart.fill")
                                        .resizable()
                                        .scaledToFit()
                                        .foregroundStyle(.red)
                                }
                            }
                        }
                        .frame(height: 30)
                    }
                }
                else {
                    Text("Keine Aktive Challenge")
                }
            }
            .navigationTitle("Challenge")
            .navigationBarTitleDisplayMode(.inline)
        }
    }
}


#Preview {
    ChallengeTabView(currentChallenge: CoreChallengeData(
        event: Event(type: "InitChallengeEvent"),
        state: CurrentState(
            data: ChallengeInformation(
                currentLives: 1,
                duration: 15,
                maxLives: 2,
                reward: 0,
                since: "2024-06-09T12:00:00"
            ),
            type: .PausedChallengeState
        )
    ))
}

数据结构体定义

enum StateType: String, Codable{
    case  NoActiveChallengeState, DelayedChallengeState, PausedChallengeState, ActiveChallengeState, BelowThresholdState
}

struct CoreChallengeData: Codable{
    let event: Event
    let state: CurrentState
}

struct CurrentState: Codable {
    let data: ChallengeInformation
    let type: StateType
    
}
struct Event: Codable {
    let type: String
}

struct ChallengeInformation: Codable {
    let currentLives: Int
    let duration: Int
    let maxLives: Int
    let reward: Int
    let since: String
}

ContentView 代码

struct ContentView: View {
    @State private var viewModel: WatchViewModel = WatchViewModel()
    @State private var vibrates = false
    @State private var showBatterySheet = false
    var body: some View {
        NavigationStack{
            NavigationLink("Challenge View"){
                ChallengeTabView(currentChallenge: viewModel.challengeMessage)
            }
            ScoreTabView(liveScores: viewModel.liveScores)
                .accessibilityLabel("Start")
                .onAppear{
                    //viewModel.sendDataMessage(for: .syncRequest)
                }
                .sheet(isPresented: $showBatterySheet) {
                    VStack{
                        Text("Batterie:")
                        Text("99%")
                            .font(.title)
                            .foregroundStyle(.green)
                    }
                }
        }
    
    }
}

解决方案

1. 让switch逻辑在currentChallenge变更时自动执行

.onAppear和.task仅在视图首次渲染时触发,要监听currentChallenge的变化,需使用**.onChange(of:)修饰符**,它会在指定变量的值发生变更时自动执行闭包内的逻辑。

修改ChallengeTabView的body部分,添加该修饰符:

var body: some View {
    NavigationStack{
        // 原有视图代码保持不变
    }
    .onChange(of: currentChallenge) { newValue in
        // 在这里编写你的switch逻辑
        switch newValue.state.type {
        case .ActiveChallengeState:
            // 处理活跃挑战逻辑:启动计时器
            isRunning = true
            timer?.invalidate()
            timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
                counter += 1
                if counter >= newValue.state.data.duration {
                    timer?.invalidate()
                    isRunning = false
                }
            }
        case .PausedChallengeState:
            // 处理暂停逻辑:停止计时器
            timer?.invalidate()
            isRunning = false
        case .NoActiveChallengeState:
            // 处理无活跃挑战逻辑:重置状态
            timer?.invalidate()
            counter = 0
            isRunning = false
        case .DelayedChallengeState:
            // 处理延迟挑战逻辑
            timer?.invalidate()
            isRunning = false
        case .BelowThresholdState:
            // 处理低于阈值逻辑
            timer?.invalidate()
            isRunning = false
        }
    }
}

注:因为CoreChallengeData是结构体(值类型),只要其内部任何成员变化,整个值会被视为新值,.onChange会自动触发。

2. 结构体 vs 类的选择

  • 结构体:适合你的数据模型(CoreChallengeData、CurrentState等)。原因:
    • 值类型天生不可变(你的结构体属性均为let),SwiftUI对值类型的变化检测更直接,值更新时依赖该值的视图会自动刷新;
    • 实现Codable更简单,无需额外处理引用关系;
    • 线程安全,不存在引用计数问题。
  • 类:适合ViewModel(比如WatchViewModel)。原因:
    • 引用类型支持持有可变状态、处理业务逻辑(如消息解码、发送);
    • 多个视图可共享同一个实例,配合@StateObject(初始化时创建)或@ObservedObject(外部传入)+@Published属性,能让视图自动响应状态变化。

额外修正:ContentView中用@State声明ViewModel是错误的,类类型应使用@StateObject来保证生命周期:

struct ContentView: View {
    @StateObject private var viewModel: WatchViewModel = WatchViewModel() // 替换@State为@StateObject
    // 其他代码保持不变
}

内容的提问来源于stack exchange,提问作者registrier rp

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 18:28:14