SwiftData Sample Data for SwiftUI Previews

Has anyone figured out how to generate sample data for a Preview Container that contains relationships? I've tried numerous ways to get a relationship, but have not had any success. I've searched around the forms and the internet to see if I can find anything, but haven't had any luck.

Here is what I have defined for my previewContainter and the sample data structure. I've followed the same pattern that is found in WWDC2023 Build an app with SwiftData.

The books and tags are created just fine in memory, and in my SwiftUI previews, I can see these items.

Here is what I currently have, but I have also tried running a query after SampleData.books.forEach(container.mainContext.insert(object: )) and then using one of the returned items for the relationship, but every query I perform in here returns an empty result.

Am I barking up the wrong tree with my approach, or is this a known issue that I have not come across?

@MainActor
let previewContainer: ModelContainer = {
    do {
        let container = try ModelContainer(
            for: [Project.self, Recipe.self, Tag.self],
            ModelConfiguration(inMemory: true)
        )

        // Add in sample data
        SampleData.books.forEach(container.mainContext.insert(object: ))
        SampleData.tags.forEach(container.mainContext.insert(object: ))
        SampleData.recipes.forEach(container.mainContext.insert(object: ))

        return container
    } catch {
        fatalError("Failed to create preview container")
    }
}()

struct SampleData {
    static let books: [Book] = {
        return (1...5).map({ Book(title: "Sample Book #\($0)") })
    }()

    static let tags: [Tag] = {
        return (1...5).map({ Tag(name: "Sample Tag #\($0)") })
    }()

    static let recipes: [Recipe] = {
        (1...5).map({ Recipe(name: "Sample Recipe #\($0)", book: Self.books.first!) })
    }()
}

Thanks

Replies

You can do an initial query for related objects but as of Xcode 15 beta 1 it won't automatically update so that could be your problem.

Unfortunately it is still terrible syntax for using a predicate with supplied data as it was for @FetchRequest, they really need to switch from property wrappers to view modifiers for fetching so we can pass in parameters that can dynamically change from parent Views and from States in current View.

struct StudentList: View {
    var department: Department
    
    @Query
    private var students: [Student]
    
    @Environment(\.modelContext)
    private var modelContext
    
    init(department: Department) {
        self.department = department
        let departmentID = department.objectID
        let predicate = #Predicate<Student> { student in
            
            
            student.department?.objectID == departmentID
            
        }
//        let query = Query(filter: filter, sort: \.name)
//
        let fd = FetchDescriptor<Student>(predicate: predicate, sortBy: [SortDescriptor(\.name)])
        let query = Query(fd, animation: .default)

        _students = query // nasty to have to implement and init and use underscore syntax to implement this simple feature
        
        
    }

Yeah, I had a feeling I was going to have to do some weird stuff for queries like that. Not surprised given it's a Property Wrapper again in terms of fetching the data.

The issue I am seeing is that when creating the Recipe sample data, I'm getting a crash and the previews don't even load. I'm thinking this has to do with InMemory issues, but im not entirely sure.

I should have added this error to my initial post last night, but it was late, so I forgot.

`NSInvalidArgumentException`. Reason: Illegal attempt to establish a relationship 'book.

Does it work if you don't try to set the book relationship in the Recipe initializer?

I think I remember seeing a post somewhere saying that both objects in the relationship need to be added to the context first. So rather than making that part of the Recipe initializer, you might need to do it in the method that creates the context and adds the objects. Maybe something like:

SampleData.books.forEach(container.mainContext.insert(object: ))
SampleData.tags.forEach(container.mainContext.insert(object: ))
SampleData.recipes.forEach { recipe in
    container.mainContext.insert(object: recipe)
    recipe.book = SampleData.books.first!
}

Hi @anthonycastelli, does the suggestion above by @610 to insert the objects before setting the relationship resolve your issue? It would be great if you could file feedback using Feedback Assistant and provide some sample code that reproduces the issue then add a comment to this reply with the feedback ID.

I quickly gave it a test and no dice. I'll put together a sample app and verify this. I can share that here as well as filing feedback for it. This part definitely feels broken.

The query suggestions from @malc did not work for me as well either, at least not with the PreviewContainer.

I'll get a sample app put together tonight and report back any findings I have 👍

If anyone does have any other suggestions, let me know

I think it would be useful to be able to see your model code, so the full sample would be nice.

Looking again at what you posted originally, I just noticed that your model container lists these types for the schema, which don't include Book: [Project.self, Recipe.self, Tag.self].

@610 That was my mistake. I was sort of trying to try other things in this project. Locally, Book.self is part of the schema. I'll put together a sample project will full source code that you'll be able to check out.

After building out a sample project, it appears some derived data stuff hit me. Building the sample app proved that all the approaches above (including what I was doing initially) work. I cleared all my Xcode cache and tried it in my project and it seemed to work. Part of my issue as to why I wasn't seeing the data either might have been that this query

@Query(
        filter: #Predicate { recipe in
            recipe.book?.title == "Sample Book #1"
        },
        sort: \.name
    )
    private var recipes: [Recipe]

doesn't work. I'm not exactly sure why this is, maybe it's just me not fully understanding how #Predicate functions, but looking at it, this should work.

The query example that @malc posted doesn't appear to work either. I have prints in place to depict this. There are 3 different versions. 2 of them return an empty array, while 1 of them doesn't even compile. Now that i'm onto Queries, this is probably out of scope of this thread, but wanted to bring it up anyways in case anyone has any ideas there.

I've attached a sample project that explains the different approaches I have used for generating Sample data, as well as how I got around the @MainActor but with the #Preview macro. I figured this would probably be a good point of reference for anyone that ends up in my shoes.

I wasn't sure of the best way to handle uploading the sample project, so I threw it in my S3 bucket. http://anth.io/SwiftDataDemo.zip