3

When the NavigationLink is pressed I want to create an object (with the time when it was pressed), add it to the savedObjects and pass then new object to the destination view. How can I do this without changing the state while the view is updating?

struct ContentView: View {

    @State private var savedObjects = [
        MyObject(
            id: 0,
            date: Date()
        ),
        MyObject(
            id: 1,
            date: Date()
        ),
        MyObject(
            id: 2,
            date: Date()
        ),
        MyObject(
            id: 3,
            date: Date()
        )
    ]

    var body: some View {
        NavigationView {
            List {
                NavigationLink("Save new object and navigate to it", destination: DestinationView(object: MyObject(id: Int.random(in: 10...1000), date: Date())))
                ForEach(savedObjects) { object in
                    NavigationLink("Navigate to object \(object.id)", destination: DestinationView(object: object))
                }
            }
        }
    }
}

class MyObject: ObservableObject, Identifiable {
    var id: Int
    var date: Date

    init(id: Int, date: Date) {
        self.id = id
        self.date = date
    }
}

struct DestinationView: View {

    @ObservedObject var object: MyObject

    var body: some View {
        VStack {
            Text("object \(object.id)")
            Text("date: \(object.date.description)")
        }
    }
}
Isaak
  • 1,107
  • 2
  • 11
  • 29

3 Answers3

1

Here is a solution that refreshes state with a new Date when the link appears.

struct LinkWithPayloadView: View {
    @State private var date = Date()
    
    var body: some View {
        NavigationView {
            navigationLink(payload: "Cookies and Milk", date: date)
                .onAppear(perform: {
                    date = Date()
                })
        }
    }
    
    func navigationLink(payload: String, date: Date) -> some View {
        let formatter = DateFormatter()
        formatter.dateFormat = "HH:mm:ss"
        let payload = Payload(timestamp: date, inners: payload)

        let vstack = VStack {
            Text("\(payload.inners)")
            Text("\(formatter.string(from: payload.timestamp))")
        }

        return NavigationLink(payload.inners, destination: vstack)
    }
    
    struct Payload {
        let timestamp : Date
        let inners : String
    }
}

Credit @mallow for the idea

Helperbug
  • 529
  • 3
  • 8
  • In my case I need to save the newly created object to CoreData. I only want to save the object in CoreData once when the NavigationLink is pressed. – Isaak May 10 '21 at 07:19
  • That sounds more like a button than a navigation link. Can you post a screenshot of your UI? It sounds like an actionsheet or popover may be an option. – Helperbug May 10 '21 at 08:46
  • For creating a new object I want to have the same behaviour as navigating to an existing one. – Isaak May 10 '21 at 09:06
0

My question is why can't you make a separate destination view to save the object? Then you don't need to worry about saving it in the starting view.

struct DestinationView: View {
@Environment(\.managedObjectContext) var managedObjectContext
@ObservedObject var object: MyObject

init(id : Int, date : Date) {
    let myObject = MyObject(context: managedObjectContext)
    myObject.date = date
    myObject.id = id
    try! managedObjectContext.save()
    self.object = myObject
}

var body: some View {
    VStack {
        Text("object \(object.id)")
        Text("date: \(object.date.description)")
    }
}
}

Then you can just reload the objects from coredata when the navigation closes.

yawnobleix
  • 1,204
  • 10
  • 21
  • This code does not work because you use managedObjectContext before self is initialized but even if I pass managedObjectContext in from ContentView I get very weird behaviour with FetchRequest in ContentView. – Isaak May 10 '21 at 09:00
  • you do not need to pass the managedObjectContext its an environment variable, set in your app file App { let context = CoreDataStore.sharedInstance.managedContext var body: some Scene { WindowGroup { ContentView().environment(\.managedObjectContext, context) } } } – yawnobleix May 10 '21 at 10:31
  • My Swift compiler disagrees. – Isaak May 10 '21 at 13:04
  • have you set the environment variable? – yawnobleix May 10 '21 at 13:23
  • For a great dive into CoreData, take a look at Lecture 12 from Stanford's CS193P: https://cs193p.sites.stanford.edu. To make sure new items and edited items have the same UX the course suggests using a draft. That way the user can also abandon edits. Great course, highly recommend the all vidoes. – Helperbug May 11 '21 at 15:00
0

I think programmatic navigation is the best solution in my case. So clicking a different button that creates and saves the object which in turn creates a new NavigationLink and then initiating the navigation immediately as explained here: https://www.hackingwithswift.com/quick-start/swiftui/how-to-use-programmatic-navigation-in-swiftui

Isaak
  • 1,107
  • 2
  • 11
  • 29