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

如何引用SwiftUI中ForEach循环创建的单个Button实例?

解决SwiftUI中ForEach生成按钮的边框闪烁交互问题

需求

  • 点击与newLetter匹配的正确按钮时,该按钮边框闪绿,随后生成新的newLetter
  • 点击错误按钮时,匹配newLetter的正确按钮边框闪红,提示用户正确选项

之前的问题

尝试过两种实现方式都没达到预期:

  1. 点击按钮时传边框颜色,但错误操作时无法指定仅正确按钮闪红
  2. 使用自定义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
                            }    
                    }
                }
        }
    }
}

解决方案

核心是让每个按钮独立管理自身的闪烁状态,同时父视图控制全局的交互逻辑:

  1. 父视图新增状态变量,记录交互状态
  2. 自定义ButtonStyle根据自身标签和父视图状态,动态调整边框颜色
  3. 点击时区分正确/错误操作,触发对应闪烁逻辑并重置状态

修改后的完整代码:

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 02:23:16