macOS SwiftUI Commands and Core Data

Hi,

I'm trying to create a new Core Data object from a Commands menu item in SwiftUI in macOS.

TL:DR; how can I make a NavigationLink in the macOS Commands menu create a new Core Data object each time it is selected rather than once?

I am using a NavigationLink to present a new window allowing the user to edit the new object. This works fine the first time; however on subsequent presentations the menu item is referencing the same item, not creating a new one.

Here's my initial code:

struct CommandMenu: Commands {
  var context: NSManagedObjectContext
  @CommandsBuilder var body: some Commands {
    CommandMenu("Objects") {
      NavigationLink(
        destination: {
          ObjectEditor(object: object)
            .environment(\.managedObjectContext, context)
        },
        label: { Text("New Object") }
      )
      .keyboardShortcut("o", modifiers: [.command, .shift])
      Divider()
      // more commands here
    }
  }
}

I've tried a number of ways to try and make the NavigationLink lazy but I can't quite seem to get it right. Here's my most recent try:

struct CommandMenu: Commands {
  var context: NSManagedObjectContext
  @CommandsBuilder var body: some Commands {
    CommandMenu("Objects") {
      LazyNavLink({ Object(context:context })
        .environment(\.managedObjectContext, context)
        .keyboardShortcut("o", modifiers: [.command, .shift])
      Divider()
      // more commands here
    }
  }
}

struct LazyNavLink: some View {
  @Environment(\.managedObjectContext) var context
  var object: () -> Object
  @ViewBuilder var body: some View {
    NavigationLink(
      destination: {
        ObjectEditor(object: object())
          .environment(\.managedObjectContext, context)
      },
      label: { Text("New Object") }
    )
  }

  init(_ object: @escaping () -> Object) {
    self.object = object
  }
}

Unfortunately this still doesn't seem to work - the object is created at the point the view appears (i.e. at app launch). Both approaches seem to work but both behave in exactly the same way which is not what I was expecting. I was hoping the @escaping declaration would make the creation of the object only happen when the menu item was selected but this doesn't seem to be the case.

Any pointers would be greatly appreciated!

Answered by robwuk in 691049022

I think I have managed to resolve this myself - I created a new object wrapper to resolve a different issue and it has ended up resolving this issue too.

import SwiftUI
import CoreData

struct ObjectsCommandMenu: Commands {
  var context: NSManagedObjectContext

  @CommandsBuilder var body: some Commands {
    CommandMenu("Objects") {
      NavigationLink(
        destination: { NewObjectWrapper(context: context) },
        label: { Text("New Object") }
      )
        .keyboardShortcut("o", modifiers: [.command, .shift])
      Divider()
      // more commands here
    }
  }
}

struct NewObjectWrapper: View {
  var context: NSManagedObjectContext
  @StateObject var object: Object

  @ViewBuilder var body: some View {
    ObjectEditor(object: object)
  }

  init(context: NSManagedObjectContext) {
    self.context = context
    self._object = StateObject(wrappedValue: Object(context: context))
  }
}
Accepted Answer

I think I have managed to resolve this myself - I created a new object wrapper to resolve a different issue and it has ended up resolving this issue too.

import SwiftUI
import CoreData

struct ObjectsCommandMenu: Commands {
  var context: NSManagedObjectContext

  @CommandsBuilder var body: some Commands {
    CommandMenu("Objects") {
      NavigationLink(
        destination: { NewObjectWrapper(context: context) },
        label: { Text("New Object") }
      )
        .keyboardShortcut("o", modifiers: [.command, .shift])
      Divider()
      // more commands here
    }
  }
}

struct NewObjectWrapper: View {
  var context: NSManagedObjectContext
  @StateObject var object: Object

  @ViewBuilder var body: some View {
    ObjectEditor(object: object)
  }

  init(context: NSManagedObjectContext) {
    self.context = context
    self._object = StateObject(wrappedValue: Object(context: context))
  }
}
macOS SwiftUI Commands and Core Data
 
 
Q