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

SwiftUI跨视图传递ExerciseSets数组遇初始化匹配错误求助

SwiftUI视图嵌套数据传递问题与@Binding报错修复

我在SwiftUI开发中遇到视图间数据传递问题,视图层级是ExSetView → SetRowView → SetRowView2,需要在ExSetView中获取SetRowView2创建的ExerciseSets数组,用它控制按钮逻辑。但在ExSetView中把setsInWorkout声明为@Binding变量时,一直报错:

No exact matches in call to initializer - Found candidate with type '(Binding<*>) -> Binding<Optional<*>>'

原代码示例

ExSetView代码

struct ExSetView: View { 
    
    @Environment(\.managedObjectContext) var viewContext
    @Environment(\.dismiss) var dismiss
    let date = Date()
    @State var selectedItems = Set<Exercise>()
    @State private var sets : Int = 1
    @Binding var setsInWorkout = [ExerciseSet]

    var body: some View {
        
        NavigationView {
            
            VStack (alignment: .leading) {
               Text("Sets in workout: \($setsInWorkout.count)")
                HStack {
                    Text(date, style: .date)                
                    Button("Done"){
                        // save workout using setsInWorkout
                    }      
                }

                ForEach(Array(selectedItems), id: \.self) { e in
                    HStack{
                        Text(e.exercisename)
                    }
                    ForEach(0..<sets, id: \.self) { number in
                        SetRowView(selectedItem: e)
                    }
                }
            }
        }      
    }
}

SetRowView代码

import SwiftUI

extension View {
    func hideKeyboard() {
        UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
    }
}

struct SetRowView: View {
    @Environment(\.managedObjectContext) var viewContext
    @Environment(\.dismiss) var dismiss
    @State var selectedItem = Exercise()
    
    var body: some View {
        
        VStack{
            Button(action: {
                self.sets += 1
            }) {
                Text("Add Set")
            }
            
            ForEach(0..<sets, id: \.self) { number in
               SetRowView2(selectedItem: selectedItem)
             
            }
        }}
  
}

SetRowView2代码

struct SetRowView2: View {
    @State private var weight = ""
    @State private var reps = ""
    @State private var sets : Int = 1
    @Environment(\.managedObjectContext) var viewContext
    @Environment(\.dismiss) var dismiss
    @State var selectedItem = Exercise()
    @State var setsInWorkout = [ExerciseSet]()
    
    func addExerciseSet() {
        let newExerciseSet = ExerciseSet(context: viewContext)
        newExerciseSet.exercise = selectedItem
        newExerciseSet.dateCompleted = Date()
        newExerciseSet.id = UUID()
        newExerciseSet.reps = Int16(reps) ?? 1
        newExerciseSet.weight = Int16(weight) ?? 1
        
        setsInWorkout.append(newExerciseSet)
        try? viewContext.save()
        dismiss()
    }
    
    var body: some View {
        
        HStack{
            ForEach(0..<sets, id: \.self) { number in
                Form {
                    Section {
                        TextField("Reps", text: $reps)
                            .frame(width:20)
                            .keyboardType(.decimalPad)
                            .onTapGesture {
                                self.hideKeyboard()
                            }
                        TextField("Weight", text: $weight)
                            .frame(width:20)
                            .keyboardType(.decimalPad)
                            .onTapGesture {
                                self.hideKeyboard()                         
                            }
                    }
                }
            }
            
            Button(action: {
                addExerciseSet()
            }) {
                Text("Done")              
            }
        }    
    }
}

解决方案

1. 修正@Binding语法错误

原代码中@Binding var setsInWorkout = [ExerciseSet]是错误写法:@Binding变量不能直接赋值类型,且如果ExSetView是根视图,应该用@State持有数据源,再通过Binding传递给子视图:

// ExSetView中修改
@State private var setsInWorkout: [ExerciseSet] = []

2. 逐层传递Binding数据

要实现底层视图数据回传到顶层,需将setsInWorkout的Binding逐层向下传递:

修改SetRowView,添加Binding参数

struct SetRowView: View {
    @Environment(\.managedObjectContext) var viewContext
    @Environment(\.dismiss) var dismiss
    let selectedItem: Exercise // 用let接收父视图传递的对象,不要用@State初始化
    @Binding var setsInWorkout: [ExerciseSet] // 添加Binding参数
    @State private var sets: Int = 1 // 补上原代码缺失的sets变量
    
    var body: some View {
        VStack{
            Button(action: {
                self.sets += 1
            }) {
                Text("Add Set")
            }
            
            ForEach(0..<sets, id: \.self) { number in
                // 传递Binding给SetRowView2
                SetRowView2(selectedItem: selectedItem, setsInWorkout: $setsInWorkout)
            }
        }
    }
}

修改SetRowView2,用@Binding接收数据

把原有的@State var setsInWorkout = [ExerciseSet]()改成@Binding,同时selectedItem用let接收:

struct SetRowView2: View {
    @State private var weight = ""
    @State private var reps = ""
    @State private var sets : Int = 1
    @Environment(\.managedObjectContext) var viewContext
    @Environment(\.dismiss) var dismiss
    let selectedItem: Exercise // 用let接收父视图传递的对象
    @Binding var setsInWorkout: [ExerciseSet] // 改成Binding类型
    
    func addExerciseSet() {
        let newExerciseSet = ExerciseSet(context: viewContext)
        newExerciseSet.exercise = selectedItem
        newExerciseSet.dateCompleted = Date()
        newExerciseSet.id = UUID()
        newExerciseSet.reps = Int16(reps) ?? 1
        newExerciseSet.weight = Int16(weight) ?? 1
        
        // 直接添加到绑定的顶层数组中
        setsInWorkout.append(newExerciseSet)
        try? viewContext.save()
        dismiss()
    }
    
    var body: some View {
        HStack{
            ForEach(0..<sets, id: \.self) { number in
                Form {
                    Section {
                        TextField("Reps", text: $reps)
                            .frame(width:20)
                            .keyboardType(.decimalPad)
                            .onTapGesture {
                                self.hideKeyboard()
                            }
                        TextField("Weight", text: $weight)
                            .frame(width:20)
                            .keyboardType(.decimalPad)
                            .onTapGesture {
                                self.hideKeyboard()                         
                            }
                    }
                }
            }
            
            Button(action: {
                addExerciseSet()
            }) {
                Text("Done")              
            }
        }    
    }
}

修改ExSetView中调用SetRowView的代码

传递setsInWorkout的Binding:

// ExSetView的ForEach中修改
ForEach(0..<sets, id: \.self) { number in
    SetRowView(selectedItem: e, setsInWorkout: $setsInWorkout)
}

3. 修正其他细节问题

  • ExSetView中显示数组数量时,直接用setsInWorkout.count,不需要加$:
Text("Sets in workout: \(setsInWorkout.count)")

核心逻辑说明

  • @Binding的作用是让多层视图共享同一数据源,子视图修改数据时,顶层视图能实时获取更新。
  • 逐层传递Binding确保从SetRowView2到ExSetView共享同一个setsInWorkout数组,添加新元素时顶层视图立即感知。
  • 子视图接收父视图传递的静态对象(如selectedItem)时,用let而非@State,避免不必要的状态持有。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.11 05:01:12