NEW: My new book Pro SwiftUI is out now – level up your SwiftUI skills today! >>

SOLVED: Passing preview data with Core Data

Forums > SwiftUI

Hi all

I'm learning Swift/SwiftUI, and have used a few of Paul's tutorials. The relevant one now is this one: https://www.hackingwithswift.com/quick-start/swiftui/how-to-configure-core-data-to-work-with-swiftui

I've created the PersistenceController, and that's working fine. It looks like this:

import CoreData
import Foundation

struct PersistenceController {
    static let shared = PersistenceController()

    // Storage for Core Data
    let container: NSPersistentContainer

    // A test configuration for SwiftUI previews
    static var preview: PersistenceController = {
        let controller = PersistenceController(inMemory: true)

        // Create an example list.
            let myList = myList(context: controller.container.viewContext)
            myList.name = "Test List"
            myList.listType = MyListType.always.description
            myList.frequency = MyListFrequency.daily.description
            myList.runsOn = MyListRunsOn.everyday.description
            myList.startDate = Date()

        return controller
    }()

    // An initializer to load Core Data, optionally able
    // to use an in-memory store.
    init(inMemory: Bool = false) {
        container = NSPersistentContainer(name: "MyModel")

        if inMemory {
            container.persistentStoreDescriptions.first?.url = URL(fileURLWithPath: "/dev/null")
        }

        container.loadPersistentStores { description, error in
            if let error = error {
                fatalError("Error: \(error.localizedDescription)")
            }
        }
    }

    func save() {
        let context = container.viewContext

        if context.hasChanges {
            do {
                try context.save()
            } catch {
                // Show some error here
            }
        }
    }
}

My ContentView has a TabView containing 4 tabs.

struct ContentView: View {

    @Environment(\.managedObjectContext) var moc

    var body: some View {
        TabView {
            HomeView()
                .tabItem {
                    Label("Home", systemImage: "house")
                }

            ListsView()
                .tabItem {
                    Label("Lists", systemImage: "list.dash")
                }

            TasksView()
                .tabItem {
                    Label("Tasks", systemImage: "text.badge.checkmark")
                }

            SettingsView()
                .tabItem{
                    Label("Settings", systemImage: "gearshape")}
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

The ListView() looks like this:

import SwiftUI

struct ListsView: View {
    @Environment(\.managedObjectContext) var moc

    @FetchRequest(entity: MyList.entity(),
                  sortDescriptors: [NSSortDescriptor (keyPath: \MyList.name, ascending: true)])
    private var myLists: FetchedResults<MyList>

    @State var showingAddListView: Bool = false

    var body: some View {
        NavigationView {
            List {
                ForEach(self.myLists, id: \.self) { myList in
                    HStack {
                        Text(myList.name ?? "Unknown")

                        Spacer()
                    }
                } //: FOREACH
                .onDelete(perform: deleteList)
            } //: LIST
            .navigationBarTitle("Lists", displayMode: .inline)
            .toolbar {
                ToolbarItem(placement: .navigationBarLeading) {
                    EditButton()
                        .accessibilityLabel("Edit Lists")
                }
                ToolbarItem {
                    Button(action: addList) {
                        Label("Add Item", systemImage: "plus")
                    }
                    .accessibilityLabel("Add Item")
                    .sheet(isPresented: $showingAddListView) {
                        AddMyListView().environment(\.managedObjectContext, self.moc)
                    }
                }
            } //: TOOLBAR
            Text("Select an item")
        } //: NAVIGATIONVIEW
    }

    private func addList(){

        showingAddListView.toggle()
    }

    private func deleteList(at offsets: IndexSet) {
        for index in offsets {
            let myList = myLists[index]
            moc.delete(myList)

            do {
                if moc.hasChanges {
                    try moc.save()
                }} catch {
                    print(error)

            }
        }
    }
}

struct Lists_Previews: PreviewProvider {
    static var previews: some View {
        ListsView()
    }
}

I now want to make a ListEditDetail view, which is simple enough. I want to click on the item in myList, which will pass the MyList in to the view. This will look like this:

import SwiftUI

struct ListDetailView: View {
    //@Binding var myList : MyList

    var body: some View {

        Text("Hello, World!")
    }
}

struct ListDetailView_Previews: PreviewProvider {
    static var previews: some View {
        ListDetailView()
    }
}

And this is where I'm stuck. I can't work out how to pass the preview data from the PersistenceController in to the Preview here.

Any help apprecated

   

@sparrow is stuck!

I can't work out how to pass the preview data from the PersistenceController into the Preview here.

struct PersistenceController {
    [... snip ...]

    // A test configuration for SwiftUI previews
    static var preview: PersistenceController = {
        let controller = PersistenceController(inMemory: true)

Because you made the preview a static variable, it's not a member of any Struct's instance. You can make several instances of the PersistenceController in your application and none will be able to access the preview var.

To access the var, you need to prepend the struct name to the variable, like so:

struct ListDetailView_Previews: PreviewProvider {

    let localPreview = PersistenceController.preview  // <-- Access the static var via the struct's name
    static var previews: some View {
        ListDetailView() // <-- use localPreview with this view struct
    }
}

@twoStraws notes this is a best practice even with simple structs.

struct FavoriteSingers {
     var name: String
     var genre: String

     // Create an example to use during debugging, or sample code
     static var exampleSinger = FavoriteSinger( name = "Taylor Swift", genre = "Pop")
 }

 // Then use this in your preview code
 struct WishListView_Previews: PreviewProvider {
     static var previews: some View {
         WishListView( nextAlbumFor: FavoriteSingers.exampleSinger )  // <-- Use static example here.
     }
 }

Hacking With Swift Article

Of course @twoStraws addresses this in a nice article. Grab a hot cuppa, get cozy, and have a good read.

See -> Static Examples

   

Apologies for the slow reply, I didn't get a notification about the reply. Is if possible to subscribe to posts to be notified if someone updates it?

I've updated the ListDetailView as you suggested:

struct MyListDetailView: View {
    @Environment(\.managedObjectContext) private var moc
    @Binding var myList : MyList

    var body: some View {

        Text("Hello, World!")
    }
}

struct MyListDetailView_Previews: PreviewProvider {
    static var previews: some View {
        let localPreview = PersistenceController.preview
        MyListDetailView(myList: localPreview)
    }
}

However, as I've created myList as a binding, I get the following error:

Cannot convert value of type 'PersistenceController' to expected argument type 'Binding<MyList>'

This is on the last line of the Previews method. I'm trying to pass the MyList in from the ForEach loop on the ListView. I think I'm mixing up State and Binding declarations.

ETA: I understand that the preview static is a PersistenceController. What I can't work out is how to extract the myList from that.

   

Anyone able to help?

   

You need a seperate preview for MyList. When working with CoreData and when you want previews for several different entities you can't just use the PersistanceController. You need to create a preview which returns the MyList object.

This can result in that you have to create many different objects before you get your desired preview. But all have in common that they must be created in the same managedobjectcontext.

Please bear in mind that every entity of CoreData is an ObservableObject. So @Binding doesn't work. You need @ObservedObject for working with entities.

   

Thanks @Hatsushira

The key bit of information I was missing was that I had to instantiate an instance of MyList. Once I figured that out, it was simple enough to do what I needed to.

1      

Hacking with Swift is sponsored by Play

SPONSORED Play is the first native iOS design tool created for designers and engineers. You can install Play for iOS and iPad today and sign up to check out the Beta of our macOS app with SwiftUI code export. We're also hiring engineers!

Click to learn more about Play!

Sponsor Hacking with Swift and reach the world's largest Swift community!

Reply to this topic…

You need to create an account or log in to reply.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.