I'm trying to make an edit form that can take a value as a @Binding, edit it, and commit the change. In this case, I'm populating a list with Core Data records using the @FetchRequest property wrapper. I want to tap on a row to navigate from the List to a Detail view, then on the Detail view I want to navigate to the Edit view.
I tried doing this without the @Binding and the code will compile but when I make an edit, it is not reflected on the previous screens. It seems like I need to use @Binding but I can't figure out a way to get a NSManagedObject instance inside of a List or ForEach, and pass it to a view that can use it as a @Binding.
List View
struct TimelineListView: View {
@Environment(\.managedObjectContext) var managedObjectContext
// The Timeline class has an `allTimelinesFetchRequest` function that can be used here
@FetchRequest(fetchRequest: Timeline.allTimelinesFetchRequest()) var timelines: FetchedResults<Timeline>
@State var openAddModalSheet = false
var body: some View {
return NavigationView {
VStack {
List {
Section(header:
Text("Lists")
) {
ForEach(self.timelines) { timeline in
// ✳️ How to I use the timeline in this list as a @Binding?
NavigationLink(destination: TimelineDetailView(timeline: $timeline)) {
TimelineCell(timeline: timeline)
}
}
}
.font(.headline)
}
.listStyle(GroupedListStyle())
}
.navigationBarTitle(Text("Lists"), displayMode: .inline)
}
} // End Body
}
Detail View
struct TimelineDetailView: View {
@Environment(\.managedObjectContext) var managedObjectContext
@Binding var timeline: Timeline
var body: some View {
List {
Section {
NavigationLink(destination: TimelineEditView(timeline: $timeline)) {
TimelineCell(timeline: timeline)
}
}
Section {
Text("Event data here")
Text("Another event here")
Text("A third event here")
}
}.listStyle(GroupedListStyle())
}
}
Edit View
struct TimelineEditView: View {
@Environment(\.managedObjectContext) var managedObjectContext
@Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
@State private var newDataValue = ""
@Binding var timeline: Timeline
var body: some View {
return VStack {
TextField("Data to edit", text: self.$newDataValue)
.shadow(color: .secondary, radius: 1, x: 0, y: 0)
.textFieldStyle(RoundedBorderTextFieldStyle())
.onAppear {
self.newDataValue = self.timeline.name ?? ""
}.padding()
Spacer()
}
.navigationBarItems(
leading:
Button(action: ({
// Dismiss the modal sheet
self.newDataValue = ""
self.presentationMode.wrappedValue.dismiss()
})) {
Text("Cancel")
},
trailing: Button(action: ({
self.timeline.name = self.newDataValue
do {
try self.managedObjectContext.save()
} catch {
print(error)
}
// Dismiss the modal sheet
self.newDataValue = ""
self.presentationMode.wrappedValue.dismiss()
})) {
Text("Done")
}
)
}
}
I should mention, the only reason I'm even trying to do this is because the modal .sheet()
stuff is super buggy.
FetchedResults
, but I guess any entry can be saved from anywhere. So you don't need a @Binding on specific entry since saving (moc.save()) it means it updates your source of truth automatically. That's my best guess. – Fabian