Swift/SwiftUI未正确接收@State变量的首次更新

bxgwgixi  于 2023-05-05  发布在  Swift
关注(0)|答案(2)|浏览(112)

我从SwiftUI视图中的switch语句中得到一些奇怪的行为。在这个视图中,当chosenParamsIndex第一次更新为初始化值以外的值(通过选择器进行选择)时,switch语句不会读取更新后的值。我已确认变量的值已正确更新。
如果chosenParamsIndex是通过在选择器外部选择测验选项(选项3-5)而首次更新的,则在单击第二个不同的选项之前,switch语句不会注册多次单击同一选项。
由于不一致的行为,我不确定这个问题的原因是什么。

struct PracticeView: View {
    @State private var showingQuizView = false
    @State var pickerChoice = 1
    @State var chosenParamsIndex = 1
    //Params: 0 = Practice 10, 1 = Practice 35, 2 = Practice 50, 3 = Mock, 4 = Difficult, 5 = Rapid Fire
    
    var body: some View {
        NavigationView {
            ScrollView {
                VStack(spacing: 20) {
                    // First rectangle with practice text
                    ZStack {
                        RoundedRectangle(cornerRadius: 25)
                            .frame(height: 200)
                            .foregroundColor(Color.blue)
                        VStack(spacing: 10) {
                            HStack(spacing: 10) {
                                Image(systemName: "chart.line.uptrend.xyaxis")
                                    .resizable()
                                    .aspectRatio(contentMode: .fit)
                                    .frame(width: 45, height: 45)
                                VStack(alignment: .leading, spacing: 2) {
                                    Text("Practice")
                                        .font(.system(size: 24, weight: .bold))
                                    Text("A selection of random questions.")
                                        .font(.system(size: 16, weight: .semibold))
                                }
                                Spacer()
                            }
                            Picker(selection: $pickerChoice, label: Text("")) {
                                Text("10").tag(0)
                                Text("35").tag(1)
                                Text("50").tag(2)
                            }

                            .pickerStyle(SegmentedPickerStyle())
                            .onChange(of: pickerChoice) { newValue in
                                chosenParamsIndex = newValue
                                print("new picker value")
                            }
                            Button(action: {
                                showingQuizView = true
                            }) {
                                Text("Begin")
                                    .font(.headline)
                                    .foregroundColor(.white)
                            }
                            .frame(maxWidth: .infinity, minHeight: 40)
                            .background(Color.yellow)
                            .cornerRadius(10)
                        }
                        .padding()
                    }
                    
                    // Second rectangle with timer, text, picker, and button
                    ZStack {
                        RoundedRectangle(cornerRadius: 25)
                            .frame(height: 150)
                            .foregroundColor(Color.blue)
                        VStack(spacing: 10) {
                            HStack(spacing: 10) {
                                Image(systemName: "stopwatch")
                                    .resizable()
                                    .aspectRatio(contentMode: .fit)
                                    .frame(width: 45, height: 45)
                                VStack(alignment: .leading, spacing: 2) {
                                    Text("Mock Test")
                                        .font(.system(size: 24, weight: .bold))
                                    Text("30 Minutes · 35 Questions")
                                        .font(.system(size: 16, weight: .semibold))
                                }
                                Spacer()
                            }
                            Button(action: {
                                chosenParamsIndex = 3
                                showingQuizView = true
                            }) {
                                Text("Begin")
                                    .font(.headline)
                                    .foregroundColor(.white)
                            }
                            .frame(maxWidth: .infinity, minHeight: 40)
                            .background(Color.yellow)
                            .cornerRadius(10)
                        }
                        .padding()
                    }
                    
                    

                    
                    // Third rectangle as a blank button
                    ZStack {
                        RoundedRectangle(cornerRadius: 25)
                            .frame(height: 150)
                            .foregroundColor(Color.blue)
                        VStack(spacing: 10) {
                            HStack(spacing: 10) {
                                Image(systemName: "exclamationmark.triangle")
                                    .resizable()
                                    .aspectRatio(contentMode: .fit)
                                    .frame(width: 45, height: 45)
                                VStack(alignment: .leading, spacing: 2) {
                                    Text("Difficult Questions")
                                        .font(.system(size: 24, weight: .bold))
                                    Text("Test your knowledge.")
                                        .font(.system(size: 16, weight: .semibold))
                                }
                                Spacer()
                            }
                            Button(action: {
                                chosenParamsIndex = 4
                                showingQuizView = true
                            }) {
                                Text("Begin")
                                    .font(.headline)
                                    .foregroundColor(.white)
                            }
                            .frame(maxWidth: .infinity, minHeight: 40)
                            .background(Color.yellow)
                            .cornerRadius(10)
                        }
                        .padding()
                    }
                    
                    //Fourth
                    ZStack {
                        RoundedRectangle(cornerRadius: 25)
                            .frame(height: 150)
                            .foregroundColor(Color.blue)
                        VStack(spacing: 10) {
                            HStack(spacing: 10) {
                                Image(systemName: "bolt")
                                    .resizable()
                                    .aspectRatio(contentMode: .fit)
                                    .frame(width: 45, height: 45)
                                VStack(alignment: .leading, spacing: 2) {
                                    Text("Rapid Fire")
                                        .font(.system(size: 24, weight: .bold))
                                    Text("3 Questions · 15 Seconds")
                                        .font(.system(size: 16, weight: .semibold))
                                }
                                Spacer()
                            }
                            Button(action: {
                                chosenParamsIndex = 5
                                showingQuizView = true
                            }) {
                                Text("Begin")
                                    .font(.headline)
                                    .foregroundColor(.white)
                            }
                            .frame(maxWidth: .infinity, minHeight: 40)
                            .background(Color.yellow)
                            .cornerRadius(10)
                        }
                        .padding()
                    }
                    
                }
                .padding()
                .navigationTitle("Practice")
            }
        }
        .fullScreenCover(isPresented: $showingQuizView) {
            switch chosenParamsIndex {
            case 0:
                QuizView(isPresented: $showingQuizView, selectedSet: questionsRand10, isQuizTimed: false, setTimer: 0)
            case 1:
                QuizView(isPresented: $showingQuizView, selectedSet: questionsRand35, isQuizTimed: false, setTimer: 0)
            case 2:
                QuizView(isPresented: $showingQuizView, selectedSet: questionsRand50, isQuizTimed: false, setTimer: 0)
            case 3:
                QuizView(isPresented: $showingQuizView, selectedSet: questionsRand35, isQuizTimed: true, setTimer: 1800)
            case 4:
                QuizView(isPresented: $showingQuizView, selectedSet: questionsDifficult, isQuizTimed: false, setTimer: 0)
            case 5:
                QuizView(isPresented: $showingQuizView, selectedSet: questionsRapidFire, isQuizTimed: true, setTimer: 15)
            default:
                EmptyView()
            }
            }
    }
}
jogvjijk

jogvjijk1#

因为chosenParamsIndex getter在body中的任何地方都没有被调用,这意味着SwiftUI的依赖跟踪不会检测到它--所以它不认为需要重新计算body,因此在设置chosenParamsIndex时不会调用body。你可以像这样强迫它:

.fullScreenCover(isPresented: $showingQuizView) { [chosenParamsIndex] in

FYI这个Swift语法被称为捕获列表。
顺便说一句,你的身体太大了,这将使它很难跟踪和解决这样的问题。尝试将其分解为仅需要它们在主体中使用的数据的小视图。

hlswsv35

hlswsv352#

我设法重现了你的问题,我同意,这有点奇怪。我不能让它工作使用相同的状态变量,你正在使用。但是我找到了一个解决方案,使用了不同形式的.fullScreenCover和一个用于选择的枚举。当涉及switch语句时,使用枚举可能是一种更干净的方法:

enum QuizChoice: Int, Identifiable {
    case practice10 = 0
    case practice35 = 1
    case practice50 = 2
    case mock = 3
    case difficult = 4
    case rapidFire = 5

    var id: Int {
        rawValue
    }
}

struct QuizView: View {
    @Binding private var isPresented: QuizChoice?
    private let selectedSet: String

    init(
        isPresented: Binding<QuizChoice?>,
        selectedSet: String,
        isQuizTimed: Bool,
        setTimer: Int
    ) {
        self._isPresented = isPresented
        self.selectedSet = selectedSet
    }

    var body: some View {
        VStack {
            Spacer()
            Text(selectedSet)
            Spacer()
        }
        .contentShape(Rectangle())
        .onTapGesture {
            isPresented = nil
        }
    }
}

struct PracticeView: View {

    @State private var quizChoice: QuizChoice?
    @State var pickerChoice = QuizChoice.practice35

    @ViewBuilder
    private func quizSheet(choice: QuizChoice) -> some View {
        switch choice {
        case .practice10:
            QuizView(isPresented: $quizChoice, selectedSet: "questionsRand10", isQuizTimed: false, setTimer: 0)
        case .practice35:
            QuizView(isPresented: $quizChoice, selectedSet: "questionsRand35", isQuizTimed: false, setTimer: 0)
        case .practice50:
            QuizView(isPresented: $quizChoice, selectedSet: "questionsRand50", isQuizTimed: false, setTimer: 0)
        case .mock:
            QuizView(isPresented: $quizChoice, selectedSet: "questionsRand35", isQuizTimed: true, setTimer: 1800)
        case .difficult:
            QuizView(isPresented: $quizChoice, selectedSet: "questionsDifficult", isQuizTimed: false, setTimer: 0)
        case .rapidFire:
            QuizView(isPresented: $quizChoice, selectedSet: "questionsRapidFire", isQuizTimed: true, setTimer: 15)
        }
    }

    var body: some View {
        NavigationView {
            ScrollView {
                VStack(spacing: 20) {
                    // First rectangle with practice text
                    ZStack {
                        RoundedRectangle(cornerRadius: 25)
                            .frame(height: 200)
                            .foregroundColor(Color.blue)
                        VStack(spacing: 10) {
                            HStack(spacing: 10) {
                                Image(systemName: "chart.line.uptrend.xyaxis")
                                    .resizable()
                                    .aspectRatio(contentMode: .fit)
                                    .frame(width: 45, height: 45)
                                VStack(alignment: .leading, spacing: 2) {
                                    Text("Practice")
                                        .font(.system(size: 24, weight: .bold))
                                    Text("A selection of random questions.")
                                        .font(.system(size: 16, weight: .semibold))
                                }
                                Spacer()
                            }
                            Picker(selection: $pickerChoice, label: Text("")) {
                                Text("10").tag(QuizChoice.practice10)
                                Text("35").tag(QuizChoice.practice35)
                                Text("50").tag(QuizChoice.practice50)
                            }

                            .pickerStyle(SegmentedPickerStyle())
                            Button(action: { quizChoice = pickerChoice }) {
                                Text("Begin")
                                    .font(.headline)
                                    .foregroundColor(.white)
                            }
                            .frame(maxWidth: .infinity, minHeight: 40)
                            .background(Color.yellow)
                            .cornerRadius(10)
                        }
                        .padding()
                    }

                    // Second rectangle with timer, text, picker, and button
                    ZStack {
                        RoundedRectangle(cornerRadius: 25)
                            .frame(height: 150)
                            .foregroundColor(Color.blue)
                        VStack(spacing: 10) {
                            HStack(spacing: 10) {
                                Image(systemName: "stopwatch")
                                    .resizable()
                                    .aspectRatio(contentMode: .fit)
                                    .frame(width: 45, height: 45)
                                VStack(alignment: .leading, spacing: 2) {
                                    Text("Mock Test")
                                        .font(.system(size: 24, weight: .bold))
                                    Text("30 Minutes · 35 Questions")
                                        .font(.system(size: 16, weight: .semibold))
                                }
                                Spacer()
                            }
                            Button(action: { quizChoice = .mock}) {
                                Text("Begin")
                                    .font(.headline)
                                    .foregroundColor(.white)
                            }
                            .frame(maxWidth: .infinity, minHeight: 40)
                            .background(Color.yellow)
                            .cornerRadius(10)
                        }
                        .padding()
                    }

                    // Third rectangle as a blank button
                    ZStack {
                        RoundedRectangle(cornerRadius: 25)
                            .frame(height: 150)
                            .foregroundColor(Color.blue)
                        VStack(spacing: 10) {
                            HStack(spacing: 10) {
                                Image(systemName: "exclamationmark.triangle")
                                    .resizable()
                                    .aspectRatio(contentMode: .fit)
                                    .frame(width: 45, height: 45)
                                VStack(alignment: .leading, spacing: 2) {
                                    Text("Difficult Questions")
                                        .font(.system(size: 24, weight: .bold))
                                    Text("Test your knowledge.")
                                        .font(.system(size: 16, weight: .semibold))
                                }
                                Spacer()
                            }
                            Button(action: { quizChoice = .difficult }) {
                                Text("Begin")
                                    .font(.headline)
                                    .foregroundColor(.white)
                            }
                            .frame(maxWidth: .infinity, minHeight: 40)
                            .background(Color.yellow)
                            .cornerRadius(10)
                        }
                        .padding()
                    }

                    //Fourth
                    ZStack {
                        RoundedRectangle(cornerRadius: 25)
                            .frame(height: 150)
                            .foregroundColor(Color.blue)
                        VStack(spacing: 10) {
                            HStack(spacing: 10) {
                                Image(systemName: "bolt")
                                    .resizable()
                                    .aspectRatio(contentMode: .fit)
                                    .frame(width: 45, height: 45)
                                VStack(alignment: .leading, spacing: 2) {
                                    Text("Rapid Fire")
                                        .font(.system(size: 24, weight: .bold))
                                    Text("3 Questions · 15 Seconds")
                                        .font(.system(size: 16, weight: .semibold))
                                }
                                Spacer()
                            }
                            Button(action: { quizChoice = .rapidFire }) {
                                Text("Begin")
                                    .font(.headline)
                                    .foregroundColor(.white)
                            }
                            .frame(maxWidth: .infinity, minHeight: 40)
                            .background(Color.yellow)
                            .cornerRadius(10)
                        }
                        .padding()
                    }

                }
                .padding()
                .navigationTitle("Practice")
            }
        }
        .fullScreenCover(item: $quizChoice, content: quizSheet)
    }
}

相关问题