为何@Published属性变更未触发SwiftUI子视图更新?
问题分析与解决方案
核心问题
你遇到的视图不更新问题,根源有两个:
CurrentChallengeView中用@State修饰taskStatus,这会让子视图持有一份独立的状态副本,父视图传递的taskStatus更新时,子视图不会同步刷新。- 若
TaskStatus是引用类型(class),修改其passed属性时,@Published的currentTaskStatus引用未改变,SwiftUI不会触发视图更新。
另外调试时无法查看@Published属性,可通过调试器查看带$前缀的属性(如$currentTaskStatus)解决。
解决方案
方案1:让子视图直接依赖ViewModel
修改CurrentChallengeView,直接引用ChallengeViewModel,这样能直接响应@Published属性的更新:
struct CurrentChallengeView: View { @Binding var result: String @ObservedObject var challengeViewModel: ChallengeViewModel var validateResult: () -> Void var nextTask: () -> Void var body: some View { NavigationView { GeometryReader { proxy in VStack { if let taskStatus = challengeViewModel.currentTaskStatus { TaskView(task: taskStatus.task, width: proxy.size.width * 0.8, result: $result) if taskStatus.passed == nil { Button { validateResult() } label: { Text("Check") .foregroundStyle(.white) .padding() .frame(width: proxy.size.width * 0.8) .background(.green) .cornerRadius(30) } } else { Button { nextTask() } label: { Text("Next") .foregroundStyle(.white) .padding() .frame(width: proxy.size.width * 0.8) .background(taskStatus.passed! ? .green : .red) .cornerRadius(30) } } } Spacer() } } .background { Image("background") .resizable() .scaledToFill() .ignoresSafeArea() } } } }
更新ChallengeView的调用代码:
struct ChallengeView: View { @StateObject var challengeViewModel: ChallengeViewModel @State var result: String = "" var body: some View { if challengeViewModel.currentTaskStatus != nil { CurrentChallengeView( result: $result, challengeViewModel: challengeViewModel, validateResult: { if let intRes = Int(result) { challengeViewModel.validateResult(result: intRes) } }, nextTask: { _ = challengeViewModel.nextTask() } ) } else { Text("No task set") } } }
方案2:将子视图的taskStatus改为@Binding
如果不想让子视图依赖ViewModel,可将taskStatus改为@Binding,绑定到ViewModel的currentTaskStatus:
struct CurrentChallengeView: View { @Binding var result: String @Binding var taskStatus: TaskStatus var validateResult: () -> Void var nextTask: () -> Void var body: some View { NavigationView { GeometryReader { proxy in VStack { TaskView(task: taskStatus.task, width: proxy.size.width * 0.8, result: $result) if taskStatus.passed == nil { Button { validateResult() } label: { Text("Check") .foregroundStyle(.white) .padding() .frame(width: proxy.size.width * 0.8) .background(.green) .cornerRadius(30) } } else { Button { nextTask() } label: { Text("Next") .foregroundStyle(.white) .padding() .frame(width: proxy.size.width * 0.8) .background(taskStatus.passed! ? .green : .red) .cornerRadius(30) } } Spacer() } } .background { Image("background") .resizable() .scaledToFill() .ignoresSafeArea() } } } }
更新ChallengeView的调用:
struct ChallengeView: View { @StateObject var challengeViewModel: ChallengeViewModel @State var result: String = "" var body: some View { if let taskStatusBinding = $challengeViewModel.currentTaskStatus { CurrentChallengeView( result: $result, taskStatus: taskStatusBinding, validateResult: { if let intRes = Int(result) { challengeViewModel.validateResult(result: intRes) } }, nextTask: { _ = challengeViewModel.nextTask() } ) } else { Text("No task set") } } }
额外注意:确保TaskStatus是值类型
如果TaskStatus是class(引用类型),即使修改passed属性,@Published的引用未改变,SwiftUI不会触发更新。需将其改为struct:
struct TaskStatus { var task: Task var result: Int var passed: Bool? }
若必须使用class,则需在修改属性后手动发送更新通知:
func validateResult(result: Int) { if let status = currentTaskStatus, let index = self.currentTaskIndex { status.passed = result == status.result self.taskStatus[index] = status objectWillChange.send() // 手动触发视图更新 } }
调试@Published属性的方法
在Xcode调试器中,查看challengeViewModel.$currentTaskStatus(注意前缀$),即可看到@Published属性的当前值。也可在ViewModel的方法中添加打印语句,跟踪状态变化。
内容的提问来源于stack exchange,提问作者Gerrit
相关产品推荐
相关产品推荐

