如何引用SwiftUI中ForEach循环创建的单个Button实例?
解决SwiftUI中ForEach生成按钮的边框闪烁交互问题
需求
- 点击与
newLetter匹配的正确按钮时,该按钮边框闪绿,随后生成新的newLetter - 点击错误按钮时,匹配
newLetter的正确按钮边框闪红,提示用户正确选项
之前的问题
尝试过两种实现方式都没达到预期:
- 点击按钮时传边框颜色,但错误操作时无法指定仅正确按钮闪红
- 使用自定义
ButtonStyle,但全局的borderColor会影响所有按钮,没法单独控制正确按钮的闪红效果
现有代码
import SwiftUI struct ContentView: View { var fullArray = ["1","2","3","4","5","6"] var topArray = ["1","2","3"] var botArray = ["4","5","6"] @State private var userPrompt = ["1","2","3","4","5","6"] @State private var newLetter = "" @State private var borderColor = Color.black @State private var letterTapped = "" func chosenLetter() -> String { return userPrompt.randomElement() ?? "Not working" } var body: some View { VStack{ Text(newLetter) .padding() .font(.title) .background(Color.blue) .foregroundColor(.white) HStack { ForEach(topArray, id: \.self) { topLetter in Button(action: { print("Top number keys") }){ Text(topLetter) } .buttonStyle(CustomButtonStyle(keyLabel: topLetter, newLetter: $newLetter, borderColor: $borderColor)) } } HStack{ ForEach(botArray, id: \.self){ botLetter in Button(action:{ print("Bottom number keys") }){ Text(botLetter) } .buttonStyle(CustomButtonStyle(keyLabel: botLetter, newLetter: $newLetter, borderColor: $borderColor)) } } .padding() } .onAppear { newLetter = chosenLetter() } } struct CustomButtonStyle: ButtonStyle { let keyLabel: String @State var isPressed = false @Binding var newLetter : String @Binding public var borderColor: Color func chooseBorder() -> Color { if keyLabel == newLetter { if isPressed{ return Color.green } }else{ return Color.black } return Color.black } func makeBody(configuration: Configuration) -> some View { configuration.label .padding() .border(chooseBorder()) .onTapGesture { isPressed = true if keyLabel == newLetter { borderColor = .green DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) { borderColor = Color.black } }else{ borderColor = .red DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) { borderColor = Color.black } } } } } }
解决方案
核心是让每个按钮独立管理自身的闪烁状态,同时父视图控制全局的交互逻辑:
- 父视图新增状态变量,记录交互状态
- 自定义
ButtonStyle根据自身标签和父视图状态,动态调整边框颜色 - 点击时区分正确/错误操作,触发对应闪烁逻辑并重置状态
修改后的完整代码:
import SwiftUI struct ContentView: View { var fullArray = ["1","2","3","4","5","6"] var topArray = ["1","2","3"] var botArray = ["4","5","6"] @State private var newLetter = "" // 标记是否需要让正确按钮闪红(点击错误时) @State private var showCorrectRedFlash = false // 标记是否点击了正确按钮 @State private var tappedCorrect = false func chosenLetter() -> String { return fullArray.randomElement() ?? "Not working" } var body: some View { VStack{ Text(newLetter) .padding() .font(.title) .background(Color.blue) .foregroundColor(.white) HStack { ForEach(topArray, id: \.self) { letter in Button(action: { handleTap(on: letter) }){ Text(letter) } .buttonStyle(CustomButtonStyle( keyLabel: letter, currentTarget: newLetter, showRedFlash: $showCorrectRedFlash, tappedCorrect: $tappedCorrect )) } } HStack{ ForEach(botArray, id: \.self){ letter in Button(action:{ handleTap(on: letter) }){ Text(letter) } .buttonStyle(CustomButtonStyle( keyLabel: letter, currentTarget: newLetter, showRedFlash: $showCorrectRedFlash, tappedCorrect: $tappedCorrect )) } } .padding() } .onAppear { newLetter = chosenLetter() } } // 统一处理按钮点击逻辑 private func handleTap(on letter: String) { if letter == newLetter { tappedCorrect = true // 闪绿后生成新目标 DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) { tappedCorrect = false newLetter = chosenLetter() } } else { showCorrectRedFlash = true // 闪红后重置状态 DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) { showCorrectRedFlash = false } } } struct CustomButtonStyle: ButtonStyle { let keyLabel: String let currentTarget: String @Binding var showRedFlash: Bool @Binding var tappedCorrect: Bool func makeBody(configuration: Configuration) -> some View { configuration.label .padding() .border(borderColor) } // 根据状态计算边框颜色 private var borderColor: Color { // 点击正确按钮时,该按钮闪绿 if keyLabel == currentTarget && tappedCorrect { return .green } // 点击错误按钮时,正确按钮闪红 if keyLabel == currentTarget && showRedFlash { return .red } // 默认黑色边框 return .black } } }
代码说明
- 父视图的
handleTap统一处理点击逻辑,区分正确/错误操作,设置对应的状态标记 CustomButtonStyle不再管理点击事件,只根据传入的状态计算边框颜色- 每个按钮根据自身标签和全局状态,自动判断是否需要闪绿/闪红,避免了全局变量影响所有按钮的问题
内容的提问来源于stack exchange,提问作者B N
相关产品推荐
相关产品推荐

