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

SwiftUI传递@State至方法后onTapGesture失效问题求助

问题

我需要根据行列数绘制网格(比如2行2列生成4个单元格),每个单元格绑定.onTapGesture,点击时更改颜色。我知道要用@State var跟踪颜色变化来触发重绘,但把@State var cellColors传入获取颜色的方法后,.onTapGesture完全不触发,这是为什么?

移除@State var cellColors时,partColor方法能正常调用,但我需要这个状态变量实现颜色变化后的自动重绘,请问我忽略了什么细节?

相关代码

View代码

struct EditGridView: View {
    
    @ObservedObject var playViewModel: PlayViewModel
    
    @State var cellColors: [Color]
    
    var body: some View {
        VStack {
            
            ForEach(0..<playViewModel.playViewState.currentInstrumentsSet.rows, id: \.self) { row in
                HStack {
                    ForEach(0..<playViewModel.playViewState.currentInstrumentsSet.columns, id: \.self) { column in
                        ZStack {
                            RoundedRectangle(cornerRadius: 7.0)
                            .fill(
                                playViewModel.partColor(
                                    row: row,
                                    column: column,
                                    cellColors: cellColors
                                )
                            )
                            .overlay(RoundedRectangle(cornerRadius: 7.0).stroke(Color("GridBorderColor")))
                            .cornerRadius(7.0)
                        }

                        .onTapGesture {
                            cellColors = playViewModel.tapOnCell(
                                index: InstrumentsSet.Track.Part.Index(row: row, column: column),
                                currentCellColors: cellColors
                            )
                        }
                    }
                }
            }
        }.aspectRatio(1.77777, contentMode: .fit)
    }
}

ViewModel中的partColor方法

public func partColor(row: Int, column: Int, cellColors: [Color]) -> Color {
        
        let index: Int = row * setSettings.gridColumns + column
        
        let color = cellColors[index]
        
        print("color \(color) at index: \(index)")
        
        return color
    }
解答

核心问题:@State的使用场景错误

@State是SwiftUI专门用来管理View内部私有状态的属性包装器,它的生命周期由SwiftUI完全托管,不能通过外部初始化参数直接传入。你现在把cellColors声明为@State var同时作为外部传入属性,会导致SwiftUI无法正确跟踪状态变化,甚至破坏View的更新逻辑,最终引发手势无法触发的问题。

解决思路与具体修改

方案:将状态移至ViewModel(推荐)

把cellColors放到PlayViewModel中,用@Published修饰,让ViewModel统一管理状态逻辑,同时SwiftUI能自动监听变化并更新View。

  1. 修改PlayViewModel
class PlayViewModel: ObservableObject {
    // 保留原有代码,新增@Published状态
    @Published var cellColors: [Color]
    
    // 初始化时传入初始颜色数组
    init(/* 原有初始化参数 */, cellColors: [Color]) {
        self.cellColors = cellColors
        // 其他初始化逻辑
    }
    
    // 简化partColor方法,无需再传cellColors参数
    public func partColor(row: Int, column: Int) -> Color {
        let index: Int = row * setSettings.gridColumns + column
        let color = cellColors[index]
        print("color \(color) at index: \(index)")
        return color
    }
    
    // 修改tapOnCell方法,直接修改ViewModel内的状态
    public func tapOnCell(index: InstrumentsSet.Track.Part.Index) {
        let cellIndex = index.row * setSettings.gridColumns + index.column
        // 这里替换成你的颜色切换逻辑,示例:
        cellColors[cellIndex] = cellColors[cellIndex] == .gray ? .orange : .gray
    }
}
  1. 修改EditGridView
struct EditGridView: View {
    
    @ObservedObject var playViewModel: PlayViewModel
    
    // 移除原有的@State var cellColors
    
    var body: some View {
        VStack {
            ForEach(0..<playViewModel.playViewState.currentInstrumentsSet.rows, id: \.self) { row in
                HStack {
                    ForEach(0..<playViewModel.playViewState.currentInstrumentsSet.columns, id: \.self) { column in
                        ZStack {
                            RoundedRectangle(cornerRadius: 7.0)
                                .fill(playViewModel.partColor(row: row, column: column))
                                .overlay(RoundedRectangle(cornerRadius: 7.0).stroke(Color("GridBorderColor")))
                                .cornerRadius(7.0)
                        }
                        .onTapGesture {
                            playViewModel.tapOnCell(index: InstrumentsSet.Track.Part.Index(row: row, column: column))
                        }
                    }
                }
            }
        }.aspectRatio(1.77777, contentMode: .fit)
    }
}

为什么这样能解决问题?

  • @Published修饰的cellColors会在值变化时自动发送通知,SwiftUI通过@ObservedObject监听ViewModel,一旦状态更新就会触发View重绘。
  • 手势触发时直接调用ViewModel的方法修改状态,避免了@State外部传入导致的生命周期混乱,手势能正常响应。

内容的提问来源于stack exchange,提问作者Symphonic eMotions

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 09:01:31