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

SwiftUI:无需点击按钮即可更新按钮内的QuizProgressView

问题解决:按钮内嵌进度条实时更新方案

问题根源

你的QuizProgressView内部创建了独立的QuizScoreManager实例,和MenuView里的管理器不是同一个对象。当分数更新时,进度条的管理器没有同步数据,只有点击按钮重建视图时才会重新加载分数,导致UI延迟更新。

解决步骤

  1. 共享状态管理器:让所有视图使用同一个QuizScoreManager实例,避免数据孤岛。
  2. 传递依赖:将MenuView的管理器传递给内嵌的QuizProgressView,而非让进度条自行创建。
  3. 全局状态提升:把管理器放到顶层的Qmodel中,确保答题后的分数更新能同步到所有相关视图。

修改后的代码

1. 调整QuizProgressView,接收外部管理器

struct QuizProgressView: View {
    var quizTitle: String
    @ObservedObject var scoreManager: QuizScoreManager // 移除内部创建,改为外部传入
    let progressViewHeight: CGFloat = 6
    let cornerRadius: CGFloat = 15
    
    var questions: [Question] {
        switch quizTitle {
        case "Core": return questionsCore
        case "Behaviour": return questionsBehaviour
        case "Parking": return questionsParking
        case "Emergencies": return questionsEmergencies
        case "RoadPosition": return questionsRoadPosition
        case "Intersection": return questionsIntersection
        case "Theory": return questionsTheory
        case "Signs": return questionsSigns
        default: return []
        }
    }

    private var lastScore: Int {
        scoreManager.getLastScore(forQuizTitle: quizTitle) ?? 0
    }

    var body: some View {
        GeometryReader { geo in
            VStack(spacing: 0) {
                let progress = Double(lastScore) / Double(questions.count)
                let progressBarWidth = geo.size.width - cornerRadius * 2
                ZStack(alignment: .leading) {
                    Capsule()
                        .foregroundColor(Color.gray.opacity(0.4))
                        .frame(height: progressViewHeight)
                    Capsule()
                        .foregroundColor(.green)
                        .frame(width: CGFloat(progress) * progressBarWidth, height: progressViewHeight)
                }
                .frame(height: progressViewHeight)
                .padding(.horizontal, cornerRadius)
            }
        }
    }
}

2. 改造MenuView,接收并传递管理器

struct MenuView: View {
    let onQuizButtonTapped: ([Question]) -> Void
    @ObservedObject var scoreManager: QuizScoreManager // 接收顶层传入的管理器
    @State private var menuSelectedSet: [Question]?
    @State private var quizOptionIndex: Int?

    var quizOptions = [
        QuizOption(title: "Core", symbolName: "book", loadSet: questionsCore),
        QuizOption(title: "Behaviour", symbolName: "figure.seated.seatbelt", loadSet: questionsBehaviour),
        QuizOption(title: "Parking", symbolName: "car.circle", loadSet: questionsParking),
        QuizOption(title: "Emergencies", symbolName: "light.beacon.max", loadSet: questionsEmergencies),
        QuizOption(title: "RoadPosition", symbolName: "car.top.lane.dashed.arrowtriangle.inward", loadSet: questionsRoadPosition),
        QuizOption(title: "Intersection", symbolName: "arrow.triangle.2.circlepath", loadSet: questionsIntersection),
        QuizOption(title: "Theory", symbolName: "questionmark.circle", loadSet: questionsTheory),
        QuizOption(title: "Signs", symbolName: "exclamationmark.octagon", loadSet: questionsSigns),
    ]
    
    var body: some View {
        NavigationView {
            ScrollView {
                VStack(spacing: 20) {
                    LazyVGrid(columns: [GridItem(.flexible()), GridItem(.flexible())], spacing: 10) {
                        ForEach(quizOptions, id: \.self) { quizOption in
                            Button(action: {
                                menuSelectedSet = quizOption.loadSet
                                onQuizButtonTapped(menuSelectedSet ?? [])
                            }) {
                                RoundedRectangle(cornerRadius: 25)
                                    .frame(height: 200)
                                    .foregroundColor(.blue)
                                    .overlay(
                                        VStack {
                                            Spacer()
                                            Image(systemName: quizOption.symbolName)
                                                .font(.system(size: 60))
                                                .foregroundColor(.white)
                                                .frame(height: 90)
                                            Text(quizOption.title)
                                                .fontWeight(.semibold)
                                                .foregroundColor(.white)
                                                .font(.title)
                                                .multilineTextAlignment(.center)
                                                .frame(height: 50)
                                            // 传入共享的scoreManager
                                            QuizProgressView(quizTitle: quizOption.title, scoreManager: scoreManager)
                                            Spacer()
                                        }
                                    )
                                    .padding(5)
                                    .shadow(color: Color.black.opacity(0.4), radius: 10, x: 0, y: 5)
                            }
                        }
                    }
                    .padding(.horizontal, 13)
                    Spacer()
                }
            }
            .navigationBarBackButtonHidden(true)
            .navigationTitle("Learn")
        }
    }
}

3. 顶层ContentView和Qmodel统一管理状态

class Qmodel: ObservableObject {
    @Published var contentSelectedSet: [Question] = []
    // 将scoreManager提升到全局模型,确保所有视图共享
    @Published var scoreManager = QuizScoreManager(startingValues: [
        "Core": 0, 
        "Behaviour": 0, 
        "Parking": 0, 
        "Emergencies": 0, 
        "RoadPosition": 0, 
        "Intersection": 0, 
        "Theory": 0, 
        "Signs": 0
    ])
}

struct ContentView: View {
    @State private var isQuizViewShowing = false
    @StateObject var model = Qmodel()
    @State var hasSelectedSet = false
    
    var body: some View {
        TabView {
            MenuView(onQuizButtonTapped: { menuSelectedSet in
                model.contentSelectedSet = menuSelectedSet
                hasSelectedSet = true
                isQuizViewShowing = true
            }, scoreManager: model.scoreManager) // 传递管理器给MenuView
            .tabItem {
                Image(systemName: "book.fill")
                Text("Learn")
            }
            PracticeView()
                .tabItem {
                    Image(systemName: "chart.bar.fill")
                    Text("Practice")
                }
            Text("SettingsView")
                .tabItem {
                    Image(systemName: "gearshape.fill")
                    Text("Settings")
                }
        }
        .fullScreenCover(isPresented: $isQuizViewShowing) {
            if hasSelectedSet {
                // 传递管理器给QuizView,答题后更新分数时能同步到进度条
                QuizView(isPresented: $isQuizViewShowing,
                         selectedSet: model.contentSelectedSet,
                         scoreManager: model.scoreManager)
            } else {
                Text("Selected set is nil")
            }
        }
    }
}

4. 确保QuizView使用同一管理器更新分数

(假设你的QuizView有更新分数的逻辑,需要接收scoreManager并调用addScore)

// 示例QuizView结构
struct QuizView: View {
    @Binding var isPresented: Bool
    var selectedSet: [Question]
    @ObservedObject var scoreManager: QuizScoreManager
    
    // ... 其他代码
    
    // 答题结束后调用此方法更新分数
    func submitScore(_ score: Int) {
        // 假设当前quiz的标题可以从selectedSet推断,或者传入
        let quizTitle = "Core" // 替换为实际的quiz标题
        scoreManager.addScore(score: score, quizTitle: quizTitle)
        isPresented = false
    }
}

原理说明

通过共享同一个QuizScoreManager实例,所有视图都观察同一个@Published的scores数组。当分数更新时,管理器的scores属性触发通知,所有依赖它的视图(包括每个按钮内的QuizProgressView)会自动刷新UI,进度条就能实时显示最新分数,无需点击按钮。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 20:28:09