Managing the screen flow seems to be less flexible with SwiftUI than it is with UIKit. The options are still numerous depending on your application and the chosen architecture (e.g. redux). Here is a trick if you have a NSManagedObject and would like to close the related screen if the object becomes obsolete.

From experience, it seems that Apple tries to automatically dismiss a screen if one of the @EnvironmentObject is removed from memory (like NSManagedObject being « deleted »). However I couldn’t find documentation about this behavior, and it didn’t work all the time (while it did most of the time).

1. Setup some context

Prerequisites:

  • Your view is related to a NSManagedObject instance.
  • The object is stored with as a @ObservedObject or similar (i.e. conforms to ObservableObject).

For the sake of this example, let’s take a simple SwiftUI view:

1
2
3
4
5
6
7
8
9
struct DetailsView: View {
    
    @EnvironmentObject var item: MyItem
    
    var body: some View {
        Text("The details of my view")
    }
    
}

Notes:

  • DetailsView is displayed modally or via a navigation link.
  • MyItem is your NSManagedObject.

2. How to determine that the item is « dead »?

I created a small extension to determine if a MO object should be used by a SwiftUI View. You could name it NSManagedObject+Usability(.swift).

1
2
3
4
5
6
7
8
9
import CoreData.NSManagedObject

extension NSManagedObject {
    
    var isUsableInView: Bool {
        return (self.isFault == false && self.isDeleted == false)
    }
    
}

Why the need? Well, if an object is deleted but a pointer has been temporary kept by a SwiftUI view, then you could end up with a crash by accessing its properties. That’s a downside of using mutable objects, and especially here of MO objects.

3. Wrap everything inside the view

Then, you should observe a change on the MO object so you can decide if it’s time to close the screen or not! It’s quite simple to do so:

1
2
3
4
5
6
var body: some View {
    Text("The details of my view")
        .onReceive(self.item.objectWillChange) { _ in
            //
        }
}

Then you just need to call the extension method and close the screen via the presentation mode (accessible via @Environment).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
struct DetailsView: View {
    
    @Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
    
    @EnvironmentObject var item: MyItem
    
    var body: some View {
        Text("The details of my view")
            .onReceive(self.item.objectWillChange) { _ in
                if self.item.isUsableInView == false {
                    self.presentationMode.wrappedValue.dismiss()
                }
            }
    }
    
}

That’s all!