SwiftUI开发报错解决:跨视图传递的数组变量无法修改,添加Goal实例失败
Hey there! Let's fix this issue step by step — the problem here boils down to how SwiftUI handles state and value types, which is a common gotcha for new SwiftUI developers.
What's Causing the Error?
SwiftUI views are value types (structs), so when you pass your goals array to AddView, you're passing a copy of the array, not a reference. Plus, regular var properties in view structs are immutable by default (you can't mutate self in a view's body or methods without using SwiftUI's state wrappers). The mutating modifier won't work here because view structs are designed to be immutable except for state-managed properties.
Step-by-Step Fix
We'll use SwiftUI's state management tools (@State and @Binding) to let AddView modify the original goals array in MyScroll.
1. Update MyScroll to Manage State
First, we'll turn goals into a @State property in MyScroll — this tells SwiftUI to track changes to the array and refresh the view when it updates.
struct MyScroll: View { var numRange: Range<Int> // Use @State to manage the mutable goals array @State private var goals: [Goal] = [] init(_ r: Range<Int>) { numRange = r // No need to initialize goals here — @State provides a default empty array } var body: some View { NavigationView{ VStack{ // Pass a binding to AddView using $goals NavigationLink(destination: AddView(goals: $goals)){ Image(systemName: "folder.badge.plus") } List(goals) { goal in HStack(alignment: .center){ Text(goal.name) } } } .navigationTitle("My Goals") // Clean up navigation title syntax } } }
2. Modify AddView to Use Binding
Change goals to a @Binding property in AddView — this lets the view reference and modify the original goals array from MyScroll instead of a copy. We'll also fix the type selection logic (since you probably want a single type, not multiple) and add guard clauses to prevent invalid goals.
struct AddView: View { // Use @Binding to connect to the parent's goals array @Binding var goals: [Goal] @State var types = ["study", "workout", "hobby", "habbit"] @State private var selected = false // Switch to single selection for goal type (more logical for this use case) @State var selection: String? @State var goalName: String = "" @State var isLongTerm: Bool = false @State var progress: [Progress] = [] var body: some View { VStack{ Text("Create your goal") // Goal name input HStack{ TextField("Name", text: $goalName) }.padding() // Goal type selection HStack{ List(types, id: \.self, selection: $selection) { Text($0) } .navigationBarItems(trailing: EditButton()) }.padding() // Long-term goal toggle HStack{ Toggle(isOn: $selected) { Text("Is your goal Long Term (no end date)") }.padding() }.padding() // Create goal button (disabled if inputs are invalid) Button(action: addGoal, label: { Text("Create Goal") }) .disabled(goalName.isEmpty || selection == nil) } .navigationTitle("Add Goal") } // Remove mutating — @Binding lets us modify the parent's array directly func addGoal(){ // Guard against empty inputs guard let goalType = selection, !goalName.isEmpty else { return } let newGoal = Goal(goalType, goalName, selected, []) goals.append(newGoal) // Reset inputs for next use goalName = "" selection = nil selected = false } }
3. Clean Up ContentView
The original goals variable in ContentView is unused, so we can remove it:
struct ContentView: View { var body: some View { TabView{ VStack{ Text("You have") Text("0") // To show real count, move @State goals to ContentView and pass it here too Text("tasks to do") }.tabItem { Text("Home")} MyScroll(1..<100).tabItem { Text("My Goals") } } } }
Bonus: Show Real Goal Count in Home Tab
If you want the Home tab to display the actual number of goals, move the @State goals property up to ContentView and pass bindings to both tabs:
struct ContentView: View { @State private var goals: [Goal] = [] var body: some View { TabView{ VStack{ Text("You have") Text("\(goals.count)") Text("tasks to do") }.tabItem { Text("Home")} MyScroll(1..<100, goals: $goals).tabItem { Text("My Goals") } } } } // Update MyScroll to accept a binding from ContentView struct MyScroll: View { var numRange: Range<Int> @Binding var goals: [Goal] init(_ r: Range<Int>, goals: Binding<[Goal]>) { numRange = r self._goals = goals } // Rest of MyScroll code stays the same }
内容的提问来源于stack exchange,提问作者siiulan

