SwiftUI dismisses the detail view when an NSManagedObject subclass instance changes in a way that moves it to a different section of a fetch request?

Viewed 90

The sample app is a default SwiftUI + Core Data template with two modifications. Detail for Item is a separate view where the user can change the timestamp. And sectioned fetch request is used as opposed to a regular one.

@SectionedFetchRequest(
    sectionIdentifier: \.monthAndYear, sortDescriptors: [SortDescriptor(\.timestamp)]
) private var items: SectionedFetchResults<String, Item>

var body: some View {
    NavigationView {
        List(items) { section in
            Section(header: Text(section.id)) {
                ForEach(section) { item in
                    NavigationLink {
                        ItemDetail(item: item)
                    } label: {
                        Text(item.timestamp!.formatted())
                    }
                }
            }
        }
    }
}
struct ItemDetail: View {
    @ObservedObject var item: Item
    
    @State private var isPresentingDateEditor = false
    
    var body: some View {
        Text("Item at \(item.timestamp!.formatted())")
            .toolbar {
                ToolbarItem(placement: .principal) {
                    Button(item.timestamp!.formatted()) {
                        isPresentingDateEditor = true
                    }
                }
            }
            .sheet(isPresented: $isPresentingDateEditor) {
                if let date = Binding($item.timestamp) {
                    DateEditor(date: date)
                }
            }
    }
}

The problem arises when the user changes the timestamp on an Item to a different month. The detail view dismisses behind the model view arbitrarily. However, the issue is not present if the user changes the timestamp to a day within the same month. It does not matter whether we use a sheet or a full-screen cover. When I was debugging this I noticed that any change of the NSManagedObject subclass instance that will change the section in which it is displayed will dismiss the detail view arbitrarily. I’m expecting to stay on the detail view even if I change the timestamp to a different month.

What is the root cause of this issue and how to fix it?

1 Answers

I think it's because the NavigationLink is no longer active because it has moved to a different section so it has a different structural identity and has defaulted back to inactive. Structural identity is explained in WWDC 2021 Demystify SwiftUI. I reported this as bug FB9977102 hopefully they fix it.

Another major problem with NavigationLink that it doesn't work when offscreen. E.g. in your sample code add lots of rows to fill up more than the screen. Scroll to top, wait one minute (so the picked updates), select first row, adjust the time to current time which will move the row to last in the list and off screen. You'll notice the NavigationLink has deactivated. People face this problem when trying to implement deep links and they can't activate a NavigationLink that is in a row that is off screen.

Related