Changes to SwiftUI FetchRequest not triggering view refresh?
Asked Answered
R

2

20

Using the FetchRequest wrapper, and changing the data of the fetched results inside the view. This works the first time the view loads, the view updates with every change made..

The problem is if I navigate away from the view and back again, the changes to fetch request data stop refreshing the view. The only way to see the changes is to navigate away and back once again.

I am printing the fetch request, and changes to the data are working, which explains why if I navigate away back again the view displays correctly, but the view is not refreshing when that data is changed on the view.

Is this a bug or am I doing something incorrectly?

I have a library view that displays all journals, in the first section is a text field that allows adding a new journal, in the second section a list of all journals that can be selected to toggle a checkmark next to them.

Adding new journals and selecting them works the first time.. Very odd that they stop working if I navigate away and come back.


struct LibraryView: View {

    // MARK: - PROPERTIES
    @Environment(\.managedObjectContext) var context


    @FetchRequest(
      //  entity: Journal.entity(),
        sortDescriptors: [NSSortDescriptor(keyPath: \Journal.name, ascending: true)],
        animation: .default
    ) var journals: FetchedResults<Journal>


    @State private var name = ""


    // MARK: - INITIAL VIEW -
    var body: some View {

        Form {

            Section(header: Text("New Journal")) {
                HStack {

                    TextField("Name", text: $name, onCommit: {
                        let newJournal = Journal(context: self.context)
                        newJournal.name = self.name
                        try? self.context.save()
                        self.name = ""
                    })

                    Button(action: { print("ACTION") }) {
                        Image(systemName: "plus.circle.fill")
                    }
                }
            }

            Section(header: Text("Journals")) {
                ForEach(journals) { journal in
                    Button(action: {
                        journal.isSelected.toggle()
                        try? self.context.save()

                    }) {
                        HStack {
                            Text(journal.name)
                            Spacer()
                            if journal.isSelected {
                                Image(systemName: "circle.fill")
                            }
                        }
                    }
                }
            }

        }.navigationBarTitle("Library")
    }
}

The journal is just a simple core data model:


import Foundation
import CoreData

public class Journal: NSManagedObject, Identifiable {

    @NSManaged public var name: String
    @NSManaged public var isSelected: Bool

}

I navigate to the Library View from a Tab View with a navigation link in the navigation bar:


struct JournalView: View {

    // MARK: - PROPERTIES
    @State private var presentFiltersView = false

    // MARK: - INITIAL VIEW -
    var body: some View {
        NavigationView {
            Text("Journal")
                .navigationBarTitle("Journal")
                .navigationBarItems(
                    leading: NavigationLink(destination: LibraryView()) {
                        Text("Library")
                    },
                    trailing: Button(action: { self.presentFiltersView = true }) {
                        Text("Filters")
                    })
        }.sheet(isPresented: $presentFiltersView) { Text("Filters") }
    }
}

I have tried removed the navigation link from the nav bar item, and using it as a standard list row navigation link, results are the same.

Rambler answered 12/10, 2019 at 18:49 Comment(0)
R
4

Think I figured out why this was happening. Problem is the way I was navigating to the view. Having a navigation link inside the navigation bar items was the cause. Instead I moved the navigation link outside on it's own, and replaced it with a button that controls the active state of the navigation link. Fetch Request seems to work as expected now.

Before:

.navigationBarItems(leading:
    NavigationLink(
        destination: MyView(),
        label: {
    Text("MyView")
}))

After:

NavigationLink(
    destination: MyView(),
    isActive: $isActive,
    label: {
        EmptyView()
})

.navigationBarItems(leading:
    Button(
        action: self.isActive = true,
        label: {
    Text("My View")
}))

Rambler answered 6/11, 2019 at 20:14 Comment(0)
M
11

Have the same issue. NavigationLink immediately instantiates destination view. Once I changed it into lazy view evaluation it begins working for me. But it still looks like a workaround.

struct LazyView<Content: View>: View {
    let build: () -> Content
    init(_ build: @autoclosure @escaping () -> Content) {
        self.build = build
    }
    var body: Content {
        build()
    }
}

See: https://www.objc.io/blog/2019/07/02/lazy-loading/

NavigationLink(destination: LazyView(LibraryView()))

Mientao answered 4/11, 2019 at 15:7 Comment(4)
Definitely gonna give this a try. Thank you!Rambler
this works flawlessly. why they immediately instantiate destinations is a mystery.Dalessio
Instantiating destinations ahead of time provides faster response when clicked on. Using the Combine framework behind the scenes lets them use the other cores for this. On the other hand it introduces the other problems we are both encountering.Motherwell
Thank you! Only thing that have worked for me. :)Derringer
R
4

Think I figured out why this was happening. Problem is the way I was navigating to the view. Having a navigation link inside the navigation bar items was the cause. Instead I moved the navigation link outside on it's own, and replaced it with a button that controls the active state of the navigation link. Fetch Request seems to work as expected now.

Before:

.navigationBarItems(leading:
    NavigationLink(
        destination: MyView(),
        label: {
    Text("MyView")
}))

After:

NavigationLink(
    destination: MyView(),
    isActive: $isActive,
    label: {
        EmptyView()
})

.navigationBarItems(leading:
    Button(
        action: self.isActive = true,
        label: {
    Text("My View")
}))

Rambler answered 6/11, 2019 at 20:14 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.