Is it possible to implement.onDelete and .onMove functionality to Core Data-backed .listStyle(GroupedListStyle()) in SwiftUI? - core-data

I'm able to get a Core Data backed flat list working (with no .listStyle modifier) with delete and move functionality.
But when I tried to make list grouped
}.listStyle(GroupedListStyle())
the wheels fall off conceptually. The onDelete modifier parameter has a function signature of IndexSet? -> Void. So I can't pass in the object to be deleted.
onMove is essentially the same problem, except worse. Both modifiers rely on a data source assumed to be a flat array of sequential values which can be accessed by IndexSet subscription. But I can't think how to build a grouped list using a flat datasource.
My view body looks like this:
//I'm building the list using two independent arrays. This makes onDelete impossible to implement as recommended
ForEach(folders, id: \.self) { folder in
Section(header: Text(folder.title) ) {
ForEach(self.allProjects.filter{$0.folder == folder}, id: \.self){ project in
Text(project.title)
//this modifier is where the confusion starts:
}.onDelete(perform: self.delete)
}
}
}.listStyle(GroupedListStyle())
func delete (at offsets: IndexSet) {
// ??.remove(atOffsets: offsets)
//Since I use two arrays to construct group list, I can't use generic remove at Offsets call. And I can't figure out a way to pass in the managed object.
}
func move (from source: IndexSet, to destination: Int) {
////same problem here. a grouped list has Dynamic Views produced by multiple arrays, instead of the single array the move function is looking for.
}

Can't you store the result of the filter and pass that on inside .onDelete to your custom delete method? Then delete would mean deleting the items inside the IndexSet. Is moving between sections possible? Or do you just mean inside each folder? If only inside each folder you can use the same trick, use the stored projects and implement move manually however you determine position in CoreData.
The general idea is the following:
import SwiftUI
class FoldersStore: ObservableObject {
#Published var folders: [MyFolder] = [
]
#Published var allProjects: [Project] = [
]
func delete(projects: [Project]) {
}
func move(projects: [Project], set: IndexSet, to: Int) {
}
}
struct MyFolder: Identifiable {
let id = UUID()
var title: String
}
struct Project: Identifiable {
let id = UUID()
var title: String
var folder: UUID
}
struct FoldersAndFilesView: View {
var body: some View {
FoldersAndFilesView_NeedsEnv().environmentObject(FoldersStore())
}
}
struct FoldersAndFilesView_NeedsEnv: View {
#EnvironmentObject var store: FoldersStore
var body: some View {
return ForEach(store.folders) { (folder: MyFolder) in
Section(header: Text(folder.title) ) {
FolderView(folder: folder)
}
}.listStyle(GroupedListStyle())
}
}
struct FolderView: View {
var folder: MyFolder
#EnvironmentObject var store: FoldersStore
func projects(for folder: MyFolder) -> [Project] {
return self.store.allProjects.filter{ project in project.folder == folder.id}
}
var body: some View {
let projects: [Project] = self.projects(for: folder)
return ForEach(projects) { (project: Project) in
Text(project.title)
}.onDelete {
self.store.delete(projects: $0.map{
return projects[$0]
})
}.onMove {
self.store.move(projects: projects, set: $0, to: $1)
}
}
}

You are correct that the key to doing what you want is getting one array of objects and grouping it appropriately. In your case, it's your Projects. You do not show your CoreData schema, but I would expect that you have a "Projects" entity and a "Folders" entity and a one-to-many relationship between them. Your goal is to create a CoreData query that creates that array of Projects and groups them by Folder. Then the real key is to use CoreData's NSFetchedResultsController to create the groups using the sectionNameKeyPath.
It's not practical for me to send you my entire project, so I will try to give you enough pieces of my working code to point you in the right direction. When I have a chance, I will add this concept into the sample program that I just published on GitHub. https://github.com/Whiffer/SwiftUI-Core-Data-Test
This is the essence of your List:
#ObservedObject var dataSource =
CoreDataDataSource<Project>(sortKey1: "folder.order",
sortKey2: "order",
sectionNameKeyPath: "folderName")
var body: some View {
List() {
ForEach(self.dataSource.sections, id: \.name) { section in
Section(header: Text(section.name.uppercased()))
{
ForEach(self.dataSource.objects(forSection: section)) { project in
ListCell(project: project)
}
}
}
}
.listStyle(GroupedListStyle())
}
Portions of CoreDataDataSource:
let frc = NSFetchedResultsController(
fetchRequest: fetchRequest,
managedObjectContext: McDataModel.stack.context,
sectionNameKeyPath: sectionNameKeyPath,
cacheName: nil)
frc.delegate = self
public func performFetch() {
do {
try self.frc.performFetch()
} catch {
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
}
private var fetchedObjects: [T] {
return frc.fetchedObjects ?? []
}
public var sections: [NSFetchedResultsSectionInfo] {
self.performFetch()
return self.frc.sections!
}
public func objects(forSection: NSFetchedResultsSectionInfo) -> [T] {
return forSection.objects as! [T]
}
public func move(from source: IndexSet, to destination: Int) {
self.reorder(from: source, to: destination, within: self.fetchedObjects)
}

If you want to easily delete things from a sectioned (doesn't have to be grouped!) List, you need to take advantage of your nesting. Consider you have the following:
List {
ForEach(self.folders) { folder in
Section(header: folder.title) {
ForEach(folder.items) { project in
ProjectCell(project)
}
}
}
}
Now you want to set up .onDelete. So let's zoom in on the Section declaration:
Section(header: Text(...)) {
...
}
.onDelete { deletions in
// you have access to the current `Folder` at this level of nesting
// this is confirmed to work with singular deletion, not multi-select deletion
// I would hope that this actually gets called once per section that contains a deletion
// but that is _not_ confirmed
guard !deletions.isEmpty else { return }
self.delete(deletions, in: folder)
}
func delete(_ indexes: IndexSet, in folder: Folder) {
// you can now delete this bc you have your managed object type and indexes into the project structure
}

Related

SwiftUI ForEach force UI update when updating the contents of a core data relationship

My app is meant to have a bunch of workouts in core data, each with a relationship to many exercises. A view should display the data in each workout (name, description etc.) and then iterate and display each exercise belonging to that workout.
Adding exercises and displaying them works fine. If an exercise is deleted, however it:
deletes from coredata no worries
the information seems to delete from iterableExercises
however, the Text line does not disappear. it goes from, for example "Squat, Description" to simply " , "
If I close the app entirely and reopen, then the " , " lines do completely disappear.
The problem code:
if let iterableExercises = workout.exercises?.array as? [ExerciseEntity] {
ForEach(iterableExercises) {exercise in
Text("\(exercise.name ?? ""), \(exercise.desc ?? "")")
}
}
I've got the entity relationship set as ordered, but I've also tried unordered with .allObjects instead of .array. This clearly isn't the problem as it's the array iterableExercises that's not correctly being reset?
EDIT: to reproduce, here's all the code you need and some screenshots of the CoreData model.
import SwiftUI
import CoreData
class ViewModel: ObservableObject {
let container: NSPersistentCloudKitContainer
#Published var savedWorkouts: [WorkoutEntity] = []
#Published var savedExercises: [ExerciseEntity] = []
// MARK: INIT
init() {
container = NSPersistentCloudKitContainer(name: "mre")
container.loadPersistentStores { description, error in
if let error = error {
print("Error loading CoreData: \(error)")
}
}
fetchWorkoutEntities()
fetchExerciseEntities()
}
// MARK: FETCHERS
func fetchWorkoutEntities() {
let request = NSFetchRequest<WorkoutEntity>(entityName: "WorkoutEntity")
do {
savedWorkouts = try container.viewContext.fetch(request)
} catch let error {
print("Error fetching WorkoutEntity: \(error)")
}
}
func fetchExerciseEntities() {
let request = NSFetchRequest<ExerciseEntity>(entityName: "ExerciseEntity")
do {
savedExercises = try container.viewContext.fetch(request)
} catch let error {
print("Error fetching ExerciseEntity: \(error)")
}
}
// MARK: SAVE
func saveData() {
do {
try container.viewContext.save()
fetchWorkoutEntities()
fetchExerciseEntities()
} catch let error {
print("Error saving: \(error)")
}
}
// MARK: ADDERS
func addWorkout(name: String) {
let _ = WorkoutEntity(context: container.viewContext)
saveData()
}
func addExerciseToWorkout(workout: WorkoutEntity, name: String) {
let newExercise = ExerciseEntity(context: container.viewContext)
newExercise.name = name
workout.addToExercises(newExercise)
saveData()
}
// MARK: DELETERS
func deleteWorkout(workout: WorkoutEntity) {
container.viewContext.delete(workout)
saveData()
}
func deleteExercise(exercise: ExerciseEntity) {
container.viewContext.delete(exercise)
saveData()
}
// MARK: TODO: UPDATERS
}
struct ContentView: View {
#StateObject var data = ViewModel()
var body: some View {
VStack {
Button {
data.addWorkout(name: "workout")
data.addExerciseToWorkout(workout: data.savedWorkouts[0], name: "[exercisename]")
} label: {
Text("Click ONCE to add workout to work with")
}
Spacer()
if let iterableExercises = data.savedWorkouts[0].exercises?.array as? [ExerciseEntity] {
ForEach(iterableExercises) { exercise in
Button {
data.deleteExercise(exercise: exercise)
} label: {
Text("Click to delete \(exercise.name ?? "") AFTER DELETING IF THIS STILL SHOWS BUT DOESN'T SHOW THE EXERCISE NAME THEN IT'S BROKEN")
}
}
}
Spacer()
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
screenshots of model
I’m not sure if this is the ONLY solution as #malhal gave quite an extensive and seemingly useful response.
But I came across a much easier and immediate fix, within my original solution. The inverse relationships must be specified. Doing this resolved all issues.
We don't use view model objects in SwiftUI. You need to learn the View struct and property wrappers which gives the consistency and efficiency of value types with the benefits of reference types. The property wrapper for core data is #FetchRequest which invalidates the View when the results change. It's also a DynamicProperty (which is how it gets the context from the environment) that you can use it directly without the property wrapper syntax which allows you to use a param in a predicate, in your case to do fetch the one-to-many relation, e.g.
struct WorkoutView: View {
private var fetchRequest: FetchRequest<Exercise>
private var exercices: FetchedResults<Exercise> {
fetchRequest.wrappedValue
}
init(workout: Workout) {
let sortAscending = true
let sortDescriptors = [SortDescriptor(\Exercise.timestamp, order: sortAscending ? .forward : .reverse)]
fetchRequest = FetchRequest(sortDescriptors: sortDescriptors, predicate: NSPredicate(format: "workout = %#", workout), animation: .default)
}
var body: some View {
List(exercises) { exercise in
ExerciseView(exercise: exercise)
}
}
}
For creating the NSPersistentContainer check out the Xcode App template with Core Data checked. Looks like this:
#main
struct TestApp: App {
let persistenceController = PersistenceController.shared
var body: some Scene {
WindowGroup {
ContentView()
.environment(\.managedObjectContext, persistenceController.container.viewContext)
}
}
The reason it is not an #StateObject is we don't want to invalidate this body when it changes and we need it to be init for previewing which is a different singleton.
struct PersistenceController {
static let shared = PersistenceController()
static var preview: PersistenceController = {
let result = PersistenceController(inMemory: true)
... see template
That other code in your view model class can be moved to NSManagedObject and NSManagedObjectContext extensions. Use the Editor menu to generate the NSManagedObject extension for the model, the files need tidying up though and make sure use extension is selected for the entity.

Core Data- Using Multiple Merge Policies

I am importing a large amount of data and persisting it via CoreData, setting constraints to ensure no data is duplicated (e.g. if it is imported multiple times). I do this via batch insert and use the merge policy NSMergeByPropertyStoreTrumpMergePolicy because any existing data in the store should not be overwritten upon import.
Later, the user can trigger some actions that require another batch insert, but with the merge policy NSMergeByPropertyObjectTrumpMergePolicy because now I want any changed attributes to have the new values persisted to the store.
This is all just as I need, except... based on my experimentation, there is no way to effectively CHANGE the merge policy once you have set it.
Once we make an insert using NSMergeByPropertyStoreTrumpMergePolicy, even if we now update context.mergePolicy to NSMergeByPropertyObjectTrumpMergePolicy, this will have no effect. Core Data will continue to generate SQL for the prior merge policy instead. Restarting the app and running the two steps in the opposite order verifies the same behavior -- whichever merge policy you specify first will become "stuck" in Core Data's behavior, even if you later update context.mergePolicy again in your code.
In fact, I found that even if I set two different merge policies on two different contexts (e.g. viewContext and backgroundContext) -- the behavior still gets "stuck". Whichever merge policy you use first will become the merge policy Core Data is using to generate SQL requests, even for the other context!
Can anybody verify if they have run into this and thoughts on workarounds?
I have found one thing -- which is to go all the way up the stack and create a second PersistenceController (and with it, NSPersistentContainer), using one for each merge policy. This works, but seems like overkill / not as intended by Apple?
Here is a simple SwiftUI-based project for testing.
You just need a CoreData model with an entity named Item, and two String attributes: col1 and col2. Set col1 as a constraint on Item.
And I recommend adding the launch argument to your scheme -com.apple.CoreData.SQLDebug 1 so you can see the SQL that Core Data is generating. More info on that here: https://useyourloaf.com/blog/debugging-core-data/
import SwiftUI
import CoreData
class Model: ObservableObject {
var dataArray = [[String:Any]]()
}
struct ContentView: View {
#State var model: Model = Model()
var body: some View {
VStack {
Button("Insert Data - PropertyStoreTrump") {
insertData()
}
Button("Insert More Data - PropertyObjectTrump") {
insertMoreData()
}
Spacer()
Button("Fetch Data") {
fetchAllData()
}
Button("Clear Data") {
clearAllData()
}
}
}
// Insert data using NSMergeByPropertyStoreTrumpMergePolicy
private func insertData() {
model.dataArray = [[String:Any]]()
model.dataArray.append(["col1":"1", "col2":"1"])
model.dataArray.append(["col1":"2", "col2":"1"])
model.dataArray.append(["col1":"3", "col2":"1"])
batchInsert(context: PersistenceController.shared.container.viewContext, mergePolicy: NSMergeByPropertyStoreTrumpMergePolicy)
}
// Insert data using NSMergeByPropertyObjectTrumpMergePolicy
private func insertMoreData() {
model.dataArray = [[String:Any]]()
model.dataArray.append(["col1":"2", "col2":"0"])
model.dataArray.append(["col1":"3", "col2":"0"])
model.dataArray.append(["col1":"4", "col2":"0"])
batchInsert(context: PersistenceController.shared.container.newBackgroundContext(), mergePolicy: NSMergeByPropertyObjectTrumpMergePolicy)
}
private func batchInsert(context: NSManagedObjectContext, mergePolicy: AnyObject) {
context.mergePolicy = mergePolicy
context.perform {
let insertRequest = NSBatchInsertRequest(entity: Item.entity(), objects: model.dataArray)
do {
let result = try context.execute(insertRequest)
} catch {
print("batch insert error: \(error)")
}
}
}
private func fetchAllData() {
let fetchRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest(entityName: "Item")
do {
let results = try PersistenceController.shared.container.viewContext.fetch(fetchRequest) as! [Item]
for result in results {
print(" \(result.col1 ?? "") \(result.col2 ?? "")")
}
} catch {
print("error with fetch: \(error)")
}
}
private func clearAllData() {
let fetchRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest(entityName: "Item")
let batchDeleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest)
do {
try PersistenceController.shared.container.viewContext.execute(batchDeleteRequest)
} catch {
print("error with delete request: \(error)")
}
}
}
struct PersistenceController {
static let shared = PersistenceController()
let container: NSPersistentContainer
init(inMemory: Bool = false) {
container = NSPersistentContainer(name: "CoreDataStackOverflow")
if inMemory {
container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
}
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
container.viewContext.automaticallyMergesChangesFromParent = true
}
}
The workaround I found is to add this line to PersistenceController
static let shared2 = PersistenceController()
then use PersistenceController.shared.container... for one merge policy
and PersistenceController.shared2.container... for the other

SwiftUI: unable to access #EnvironmentObject in view model

I'm building an app using SwiftUI / Combine and trying to do so in an MVVM pattern. I'm getting a little confused as to how best to expose certain properties and in particular, in relation to the Core Data implementation.
In the main app file, I have set up an environmnt object as follows (I'll come to why later):
struct Football_GuruApp: App {
let persistenceController = PersistenceController.shared
#StateObject var favouritePlayers = FavouritePlayersViewModel()
var body: some Scene {
WindowGroup {
ContentView()
.environment(\.managedObjectContext, persistenceController.container.viewContext)
.environmentObject(favouritePlayers)
}
}
}
The app has 3 main views:
ContentView: this contains a TabView with 2 subviews: FetchedResultsView() and FavouritesView()
FetchedResultsView: this contains a subview FetchedPlayers() which looks like this:
struct FetchedPlayersView: View {
#EnvironmentObject var fetchedResultsVM: FetchedResultsViewModel
var body: some View {
Section(header: Text("Players")) {
ForEach(fetchedResultsVM.fetchedPlayers, content: { player in
PlayerView(player: player)
})
if fetchedResultsVM.playersExpandable {
MoreResultsButton(action: fetchedResultsVM.getMorePlayers, buttonTitle: "More players")
}
}
}
}
And finally FavouritesView:
struct FavouritesView: View {
#EnvironmentObject var favouritePlayersVM: FavouritePlayersViewModel
var context = PersistenceController.shared.container.viewContext
var body: some View {
List {
ForEach(context.fetchAll(PlayerCD.self)) { player in
PlayerView(player: PlayerViewModel.mapFromCoreData(player: player))
}
}
}
}
Within the PlayerView() (subview of FetchedPlayersView) we have a button:
FavouritesButton(playerViewModel: player)
When tapped we set a property on the PlayerViewModel to true:
playerViewModel.favourite = true
And then a didSet method on PlayerViewModel triggers the player to be stored to core data:
var favourite = false {
didSet {
self.mapToCoreData()
}
}
func mapToCoreData() {
let storedPlayer = PlayerCD(context: context)
storedPlayer.id = self.id
storedPlayer.firstName = self.firstName
storedPlayer.secondName = self.secondName
try? PersistenceController.shared.container.viewContext.save()
favouritePlayersVM.updateFavourites()
}
We have the following env object on the PlayerViewModel
#EnvironmentObject var favouritePlayersVM: FavouritePlayersViewModel
Finally, FavouritePlayersViewModel looks like this:
class FavouritePlayersViewModel: ObservableObject {
#Published var players = [PlayerViewModel]()
func updateFavourites() {
let context = PersistenceController.shared.container.viewContext
let savedPlayers = context.fetchAll(PlayerCD.self)
self.players = [PlayersViewModel]()
savedPlayers.forEach {
players.append(PlayerViewModel.mapFromCoreData(player: $0))
}
}
}
So the idea is that when the button is tapped, we store to core data and then at the same time we update the players list on FavouritePlayersViewModel which should then update the FavouritesView with the latest players. However, clearly I am struggling with the implementation of the environment object. I had thought that by exposing this at the root of the app, I would be able to access everywhere, but I guess that as PlayerViewModel, for example, is not a direct descendent, I can't access (as I'm getting a crash).
Perhaps using the env object is not the best fit here, but I'm struggling to work out how best to have it so that I can update the FavouritesViewModel players list from the PlayerViewModel whilst using the same instance of this FavouritesViewModel to update the FavouritesView.
By the same token, I'm also not able to access the NSManagedObjectContext that I set as #Environment in the root file in the view models either which is why I'm using the singleton of the persistent container to store my core data, which is not what I really wanted to do.

Saving Item in CoreData Initiates Navigation

In my ContentView I have a FetchRequest<Project>. I navigate to ProjectView using a NavigationLink. From ProjectView I navigate to AddItemView using another NavigationLink. In AddItemView when I add an Item to the Project and call container.viewContext.save() the AddItemView automatically dismisses back to the ContentView.
My guess is that saving to CoreData updates the FetchRequest<Project> list which in turn updates the views, but I am not sure.
How can I save a new Item to the Project in CoreData and only navigate back to ProjectView and not ContentView?
To reproduce:
Create new Single View App and check Core Data and Host in CloudKit
In the .xcdatamodel delete the default Entity and replace it with an Entity called Project which has attributes date: Date and title: String and an Entity called Item which has an attribute name: String. Give the Project a relationship called items (to type Item) and choose “to many” on the right. Give the Item a relationship called project that is the inverse of items.
Replace the code in Persistence.swift with this:
// Persistence.swift
import CoreData
struct PersistenceController {
static let shared = PersistenceController()
let container: NSPersistentCloudKitContainer
init(inMemory: Bool = false) {
container = NSPersistentCloudKitContainer(name: "CoreDataBug")
if inMemory {
container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
}
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
/*
Typical reasons for an error here include:
* The parent directory does not exist, cannot be created, or disallows writing.
* The persistent store is not accessible, due to permissions or data protection when the device is locked.
* The device is out of space.
* The store could not be migrated to the current model version.
Check the error message to determine what the actual problem was.
*/
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
}
}
Copy ContentView
// ContentView.swift
import SwiftUI
struct ContentView: View {
#Environment(\.managedObjectContext) var moc
let projects: FetchRequest<Project>
init() {
projects = FetchRequest<Project>(entity: Project.entity(), sortDescriptors: [
NSSortDescriptor(keyPath: \Project.date, ascending: false)
])
}
var body: some View {
NavigationView {
List {
ForEach(projects.wrappedValue) { project in
NavigationLink(destination: ProjectView(project: project)) {
Text(project.title ?? "Title")
}
}
}
.navigationTitle("Projects")
.toolbar {
ToolbarItem(placement: ToolbarItemPlacement.navigationBarTrailing) {
Button {
withAnimation {
let project = Project(context: moc)
let now = Date()
project.date = now
project.title = now.description
try? moc.save()
}
} label: {
Label("Add Project", systemImage: "plus")
}
}
}
}
}
}
Create ProjectView.swift and copy this:
// ProjectView.swift
import SwiftUI
struct ProjectView: View {
#ObservedObject var project: Project
var items: [Item] {
project.items?.allObjects as? [Item] ?? []
}
var body: some View {
List {
ForEach(items) { item in
Text(item.name ?? "")
}
}
.toolbar {
ToolbarItem(placement: ToolbarItemPlacement.navigationBarTrailing) {
NavigationLink(destination: AddItemView(project: project)) {
Label("Add Item", systemImage: "plus")
}
}
}
}
}
Create AddItemView.swift and copy this:
import SwiftUI
// AddItemView.swift
import SwiftUI
struct AddItemView: View {
#Environment(\.presentationMode) var presentationMode
#Environment(\.managedObjectContext) var moc
let project: Project
#State private var selectedName: String = ""
var body: some View {
TextField("Type name here", text: $selectedName)
.navigationTitle("Add Item")
.navigationBarItems(trailing: Button("Add") {
let ingestion = Item(context: moc)
ingestion.project = project
ingestion.name = selectedName
try? moc.save()
presentationMode.wrappedValue.dismiss()
})
}
}
Run the app. Click the plus on the top right. Click the project that just slid in. In the ProjectView click the plus on the top right again. Type a name in the TextField and click add on the top right. When the AddItemView is dismissed it probably went back to ContentView. If not add another item to the project.

Using CoreData Objects in a List as Environment Object

I'm currently creating a News-Feed-Reader App with SwiftUI.
I'm fetching the feed-items and storing them in CoreData
I'd like to display the objects in a List containing NavigationLinks to a Detail View and automatically mark them as read when clicking on them.
I'm currently fetching the objects and putting them in a ObservableObject.
This is the ObservableObject class:
final class FeedItem: ObservableObject, Hashable {
static func == (lhs: FeedItem, rhs: FeedItem) -> Bool {
return lhs.item.pubDate == rhs.item.pubDate && lhs.item.articleUrl == rhs.item.articleUrl
}
func hash(into hasher: inout Hasher) {
hasher.combine(item.articleUrl)
}
let objectWillChange = PassthroughSubject<Void, Never>()
init(item: NewsItem) {
self.item = item
}
// NewsItem is the Managed Object
var item: NewsItem
var bookmarked: Bool {
set {
//This function fetches the Object and marks it as bookmarked
DatabaseManager().markAs(item: item, .read, newValue)
self.item.bookmarked = newValue
}
get {
self.item.bookmarked
}
}
var read: Bool {
set {
//This function fetches the Object and marks it as read
DatabaseManager().markAs(item: item, .read, newValue)
self.item.read = newValue
}
get {
self.item.read
}
}
}
In the moment Im creating an environment Object (EO) containing an array of all ObservableObjects
This EO is passed down to the list and whenever Im clicking on an item Im setting its read value to true thereby changing the Core Data Object.
This is the list:
#EnvironmentObject var feed: // THe array of ObservableObjects
List() {
ForEach(feed.items.indices, id:\.self) { i in
Button(action: {
self.feed.items[i].read = true
self.selectedItem = i
self.showDetail = true
}) {
ListFeedItem(item: self.$feed.items[i])
}
}
}
This method is quite slow. Whenever I'm opening the Detail View and going back a few seconds later the List-Item takes multiple seconds to refresh.
Any ideas on how I could improve this?

Resources