List not updating after inserting new value from another target SwiftUI - core-data

I'm trying to setup a action extension for my app. The action extension lets you save text to the main app thought a shared CoreData container.
The problem is that when I save text from the extension and return to the main app the updated data is not automatically loaded. The data added thought the extension does show up when I restart the app.
struct RecentsView: View {
#Environment(\.managedObjectContext) private var viewContext
#FetchRequest(sortDescriptors: [], predicate: nil, animation: .default)
private var masterClips: FetchedResults<Clip>
var body: some View {
NavigationView {
List {
ForEach(masterClips, id: \.self) { item in
Text(item.text!)
}
}
}
}
}

Related

SwiftUI + Core Data: list doesn't refresh changes (no relationship)

Recently I have integrated Core Data to my SwiftUI App.
Just to simplify, I have two views:
TimerListView: fetch data from Core Data and show the list
TimerDetailView: show Timer details and update data
struct TimerListView: View {
#Environment(\.managedObjectContext) private var viewContext
#FetchRequest(
sortDescriptors: [NSSortDescriptor(keyPath: \Timer.id, ascending: true)],
animation: .default)
private var timers: FetchedResults<Timer>
var body: some View {
NavigationView{
List {
ForEach(timers) { timer in
NavigationLink(destination: TimerDetailView(timer: timer)) {
TimerCardView(timer: timer)
}
}
}
}
[...]
}
}
Where
struct TimerDetailView: View {
#Environment(\.managedObjectContext) private var viewContext
#ObservedObject var timer: Timer
Outcomes
Data is correctly updated in Core Data (if I close and reopen the App data is correctly updated)
When from TimerDetailView I turn back to TimerListView (after an update) the list doesn't show the updated data
SOLVED: all child classes view have to define Timer attribute using #ObservedObject wrapper.
In previous code I replaced, into TimerCardView(timer: timer):
from var timer: Timer
to #ObservedObject var timer: Timer
And now WORKS!

Save updates to Core Data object

I have a Core Data object that is created in one tab and located in another via UUID. It can be updated in the second tab by clicking on the "Save" button.
Currently, when the "Save" button is clicked, the changes to the object are shown across all sheets, but when I close the app and open it again the object has gone back to how it was before.
The "Save" button currently has a similar logic to in this post. I think what could be missing is to add try viewContext.save to the "Save" button, but I'm not sure how to do this without creating a new object (as opposed to updating the existing one.
Any ideas? Thank you! (and please let me know if I need to add more context, I think this is the right amount but wasn't totally sure).
import SwiftUI
import CoreData
struct ModifySheet: View {
#Environment(\.managedObjectContext) private var viewContext
var fetchRequest: FetchRequest<Items>
var items: FetchedResults<Items> {
fetchRequest.wrappedValue
}
#State var newCost: Double = 0
var body: some View {
VStack {
Form {
Slider(value: $newCost, in: 0...100)
}
Button(action: {
items[0].cost = newCost
})
{Text("Save")}
}
}
init(filter: UUID) {
fetchRequest = FetchRequest<Item>(entity: Item.entity(), sortDescriptors: [], predicate: NSPredicate(format: "id == %#", filter as CVarArg))
}
}

#Binding - passing FetchedResults, single Entities, to SubViews in Core Data + SwiftUI

I just want to pass a binding to an Core Data - Entity to a SwiftUI SubView screen inside a ForEach Loop with a binding, so I have access to edit properties of the Entity, I can save my context and get automatic updated views..
how can I achieve something like this:
ContentView {
#Environment(\.managedObjectContext) private var viewContext
#FetchRequest(entity:ToDo.entity(), sortDescriptors: [])
private var toDoItems: FetchedResults<ToDo>
... the following part is what I need:
ForEach(toDoItems) { (item:ToDo) in
NavigationLink(
destination: MyEditView($item),
...
}
}
You need to set an #ObservedObject var item: ToDo. Your CoreData entity is a class that conforms to ObservableObject and will force a view update when any property is changed in it.
struct NextView: View {
#Environment(\.managedObjectContext) var managedObjectContext
#ObservedObject var item: ToDo
var body: some View {
Button(action: {
item.name = "New value"
if managedObjectContext.hasChanges {
do {
try self.managedObjectContext.save()
print("SAVED CONTEXT")
} catch let error {
print("Error: SAVING CONTEXT \(error), \(error.localizedDescription)")
}
}, label: {
Text("\(item.name)")
})
}
}
You can now make any change you want and save the context when needed

How do you update a TabView in SwiftUI when the CoreData is modified?

I am trying to modify the underlying CoreData for a TabView in SwiftUI and have that reflected when a user creates or deletes data, but the view never gets "redrawn" Here is what I have so far:
struct AddUserView: View {
#State var showingAddNewUser = false
#Environment(\.managedObjectContext) var managedObjectContext
#FetchRequest(
entity: Users.entity(),
sortDescriptors: [
NSSortDescriptor(keyPath: \User.fName, ascending: true)
]
) var fetchedUsers: FetchedResults<Users>
var body: some View {
TabView{
if(fetchedUsers.count > 0){
ForEach(fetchedUsers) { user in
UserProfileView(showingAddNewUser: showingAddNewUser, user: user, managedObjectContext: _managedObjectContext)
}
AddUserView(showingAddNewuser: false)
} else{
AddUserView(showingAddNewuser: false)
}
}.frame(width: UIScreen.main.bounds.width, height: 400)
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
}
For the UserProfileView I have:
struct UserProfileView: View {
#State var showingAddNewUser = false
#ObservedObject var user: Users
#Environment(\.managedObjectContext) var managedObjectContext
var body: some View {
the body of my user profile view.. Essentially some Text views to display user info along with a delete button
after editing the context is saved here
private func saveContext() {
do {
try managedObjectContext.save()
} catch {
print("Error saving managed object context: \(error)")
}
}
When I use the delete button the user is deleted but the TabView in the AddUserView is not redrawn/updated. How do I recall the "fetch" request to update the data set?

SwiftUI and FetchRequest : delay the reordering of a list

I have written an application displaying a list of students. I use a List of NavigationLink for that purpose. The students are ordered by one of their properties questionAskedClass which is an integer. All this information is stored within CoreData.
#Environment(\.managedObjectContext) var managedObjectContext
#FetchRequest(entity: Student.entity(),
sortDescriptors: [NSSortDescriptor(keyPath: \Student.questionAskedClass,
ascending: true)])
var students: FetchedResults<Student>
var body: some View {
NavigationView {
List {
ForEach(students) {student in
NavigationLink(destination: StudentView(student: student)) {
HStack {
VStack(alignment: .leading) {
Text("\(student.firstName)").font(.headline).foregroundColor(Color.black)
Text("\(student.lastName)").font(.headline).foregroundColor(Color.gray)
}
}
}
}
}
}
}
When I press the name of the students, I switch to a new view called StudentView where I can get more information about the student and where I can update the property questionAskedClass
struct StudentView: View {
#Environment(\.managedObjectContext) var managedObjectContext
func askQuestion() {
self.managedObjectContext.performAndWait {
self.student.questionAskedClass += 1
try? self.managedObjectContext.save()
}
}
}
Unfortunately, when I change that property, the ordering of the initial list is changed and I am taken away from the StudentView. The framework seems to get the feeling that the list needs to be reordered. But I just want this list to be reordered when I go back to the list. Not immediately when I change the value of questionAskedClass.
What can I do to mitigate this problem?
Thanks for your help.
You can try creating a simple NSFetchRequest<Student> and use the result of this fetch to update your students list.
#State var students: [Student] = []
fun refresh() {
let fetchRequest = NSFetchRequest<Student>(entityName: "Student")
students = try? managedObjectContext.fetch(fetchRequest) ?? []
}
You can trigger this refresh in onAppear so the list will be updated every time the View appears:
NavigationView {
...
}.onAppear {
self.refresh()
}
Alternatively you can save your context in onAppear of the main view instead of StudentView:
struct StudentView: View {
func askQuestion() {
self.student.questionAskedClass += 1
}
}
NavigationView {
...
}.onAppear {
try? self.managedObjectContext.save()
}
If you want your data to persist when the app is terminated, add this function in AppDelegate (assuming your persistentContainer is declared there:
func applicationWillTerminate(_ application: UIApplication) {
try? persistentContainer.viewContext.save()
}

Resources