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也无响应。
问题根源
- 状态隔离:
IndexCardSides内部的flipped状态是私有@State,QuizView无法感知卡片是否完成翻转,导致quizState一直停留在QUIZ_ACTIVE,按钮始终显示「show solution」(甚至初始就显示,不符合预期) - 按钮显示逻辑错误:原代码中按钮始终显示,未根据
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
相关产品推荐
相关产品推荐

