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

SwiftUI索引卡片点击翻转后按钮不显示问题求助

SwiftUI 索引卡片组件按钮不显示问题修复

问题背景

我实现了两个SwiftUI组件:IndexCardSides负责卡片翻转效果,QuizView作为容器控制卡片切换和按钮状态,但点击卡片翻转后按钮未按预期显示。

IndexCardSides 原代码

import SwiftUI

struct IndexCardSides<Front, Back>: View where Front: View, Back: View {
    
    var front: () -> Front
    var back: () -> Back
    
    @State var flipped : Bool = false
    @State var indexCardRotation : Double = 0.0
    @State var textRotation : Double = 0.0
    
    init(@ViewBuilder front: @escaping () -> Front, @ViewBuilder back: @escaping () -> Back) {
        self.front = front
        self.back = back
    }
    
    var body: some View {
        VStack {
            if flipped {
                back()
            } else {
                front()
            }
        }
        .rotation3DEffect(.degrees(textRotation), axis: (x: 0, y: 1, z: 0))
        .padding() // Text Padding
        .frame(height: 150)
        .frame(maxWidth: .infinity)
        .overlay {
            RoundedRectangle(cornerRadius: 25.0)
                .stroke(Color("BorderColor"), style: StrokeStyle(lineWidth: 2))
        }
        .padding() // Rectangles Padding
        .onTapGesture {
            flipIndexCard()
        }
        .rotation3DEffect(.degrees(indexCardRotation), axis: (x: 0, y: 1, z: 0))
    }
    
    func flipIndexCard() {
        let animationTime = 0.5
        
        withAnimation(Animation.linear(duration: animationTime)) {
            indexCardRotation += 180
        }
        
        withAnimation(Animation.linear(duration: 0.001).delay(animationTime / 2)) {
            textRotation += 180
            flipped.toggle()
        }
    }
}

QuizView 原代码

import SwiftUI

enum QuizState {
    case SOLUTION_VISIBLE, QUIZ_ACTIVE, QUIZ_FINISHED
}

struct QuizView: View {
    
    @State var learningUnit: LearningUnit
    @State var currentInformationIndex: Int = 0
    @State private var isFlipped : Bool = false
    
    @State var quizState: QuizState = QuizState.QUIZ_ACTIVE
    
    @Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
    
    var body: some View {
        VStack {
            IndexCardSides(
                front: {
                    Text(learningUnit.information[currentInformationIndex].part01)
                        .multilineTextAlignment(.center)
                }, back: {
                    Text(learningUnit.information[currentInformationIndex].part02)
                        .multilineTextAlignment(.center)
                }
            )
            
            Button(action: {
                if quizState == QuizState.QUIZ_ACTIVE {
                    if currentInformationIndex == learningUnit.information.count - 1 {
                        quizState = QuizState.QUIZ_FINISHED
                    } else {
                        quizState = QuizState.SOLUTION_VISIBLE
                    }
                } else if quizState == QuizState.SOLUTION_VISIBLE {
                    currentInformationIndex = currentInformationIndex + 1
                    quizState = QuizState.QUIZ_ACTIVE
                } else {
                    presentationMode.wrappedValue.dismiss()
                }
            }) {
                if quizState == QuizState.QUIZ_ACTIVE {
                    Label("show solution", systemImage: "eye")
                } else if quizState == QuizState.SOLUTION_VISIBLE {
                    Label("Next", systemImage: "arrow.right")
                } else {
                    Label("Done", systemImage: "checkmark")
                }
            }
        }
        .navigationTitle("Questions")
    }
}

预期功能

  • 用户打开QuizView时,仅显示索引卡片
  • 点击翻转卡片后,卡片下方显示对应按钮:非最后一张显示「Next」,点击切换至下一张;最后一张显示「Done」,点击关闭视图
  • 点击按钮切换卡片后,按钮消失,仅显示新卡片

问题描述

点击索引卡片翻转后,按钮未按预期显示;尝试在IndexCardSides上添加额外onTapGesture也无响应。


问题根源

  1. 状态隔离:IndexCardSides内部的flipped状态是私有@State,QuizView无法感知卡片是否完成翻转,导致quizState一直停留在QUIZ_ACTIVE,按钮始终显示「show solution」(甚至初始就显示,不符合预期)
  2. 按钮显示逻辑错误:原代码中按钮始终显示,未根据quizState控制显示/隐藏

修复方案

1. 同步卡片翻转状态到父视图

将IndexCardSides的flipped改为@Binding,让QuizView可以感知并控制卡片状态:

修改后的IndexCardSides:

import SwiftUI

struct IndexCardSides<Front, Back>: View where Front: View, Back: View {
    
    var front: () -> Front
    var back: () -> Back
    // 改为Binding,让父视图感知翻转状态
    @Binding var flipped: Bool
    @State var indexCardRotation : Double = 0.0
    @State var textRotation : Double = 0.0
    
    init(@ViewBuilder front: @escaping () -> Front, @ViewBuilder back: @escaping () -> Back, flipped: Binding<Bool>) {
        self.front = front
        self.back = back
        self._flipped = flipped
    }
    
    var body: some View {
        VStack {
            if flipped {
                back()
            } else {
                front()
            }
        }
        .rotation3DEffect(.degrees(textRotation), axis: (x: 0, y: 1, z: 0))
        .padding() // Text Padding
        .frame(height: 150)
        .frame(maxWidth: .infinity)
        .overlay {
            RoundedRectangle(cornerRadius: 25.0)
                .stroke(Color("BorderColor"), style: StrokeStyle(lineWidth: 2))
        }
        .padding() // Rectangles Padding
        .onTapGesture {
            flipIndexCard()
        }
        .rotation3DEffect(.degrees(indexCardRotation), axis: (x: 0, y: 1, z: 0))
        // 切换到新卡片时重置旋转状态
        .onChange(of: flipped) { newValue in
            if !newValue {
                indexCardRotation = 0
                textRotation = 0
            }
        }
    }
    
    func flipIndexCard() {
        let animationTime = 0.5
        
        withAnimation(Animation.linear(duration: animationTime)) {
            indexCardRotation += 180
        }
        
        withAnimation(Animation.linear(duration: 0.001).delay(animationTime / 2)) {
            textRotation += 180
            flipped.toggle()
        }
    }
}

2. 调整QuizView逻辑,关联翻转状态与按钮显示

修改QuizView,绑定卡片翻转状态,根据翻转结果更新quizState,并控制按钮显示:

修改后的QuizView:

import SwiftUI

enum QuizState {
    case SOLUTION_VISIBLE, QUIZ_ACTIVE, QUIZ_FINISHED
}

struct QuizView: View {
    
    @State var learningUnit: LearningUnit
    @State var currentInformationIndex: Int = 0
    @State private var isFlipped = false
    
    @State var quizState: QuizState = QuizState.QUIZ_ACTIVE
    
    @Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
    
    var body: some View {
        VStack {
            IndexCardSides(
                front: {
                    Text(learningUnit.information[currentInformationIndex].part01)
                        .multilineTextAlignment(.center)
                }, back: {
                    Text(learningUnit.information[currentInformationIndex].part02)
                        .multilineTextAlignment(.center)
                },
                // 绑定翻转状态
                flipped: $isFlipped
            )
            
            // 仅当非QUIZ_ACTIVE状态时显示按钮
            if quizState != .QUIZ_ACTIVE {
                Button(action: {
                    if quizState == .SOLUTION_VISIBLE {
                        // 切换下一张卡片,重置翻转状态
                        currentInformationIndex += 1
                        isFlipped = false
                        quizState = .QUIZ_ACTIVE
                    } else {
                        // 完成测试,关闭视图
                        presentationMode.wrappedValue.dismiss()
                    }
                }) {
                    if quizState == .SOLUTION_VISIBLE {
                        Label("Next", systemImage: "arrow.right")
                    } else {
                        Label("Done", systemImage: "checkmark")
                    }
                }
            }
        }
        .navigationTitle("Questions")
        // 监听卡片翻转状态,更新quizState
        .onChange(of: isFlipped) { newValue in
            guard newValue else { return }
            // 卡片翻转到背面,判断是否是最后一张
            if currentInformationIndex == learningUnit.information.count - 1 {
                quizState = .QUIZ_FINISHED
            } else {
                quizState = .SOLUTION_VISIBLE
            }
        }
    }
}

关键修改说明

  • 将IndexCardSides的私有翻转状态改为@Binding,实现父子视图状态同步
  • 在QuizView中通过.onChange监听卡片翻转事件,自动切换quizState
  • 按钮仅在quizState不为QUIZ_ACTIVE时显示,符合初始仅显示卡片的需求
  • 切换下一张卡片时重置isFlipped为false,同时触发IndexCardSides重置旋转状态,恢复正面显示

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 14:48:14