10

I am trying to work with multiple views in a SwiftData based SwiftUI project. However, when I create a parameter for a SwiftData @Model object in another view, when I create a sample of that object in the #Preview, the compiler starts complaining about not implementing protocols in the @Model object.

Xcode 15 beta 2

I started with a new SwiftData project for iOS. I updated ContentView like this:

import SwiftUI
import SwiftData

struct ContentView: View {
    @Environment(\.modelContext) private var modelContext
    @Query private var items: [Item]
    
    @State private var selection: Item?
    
    var body: some View {
        NavigationSplitView {
            List(selection: $selection) {
                ForEach(items) { item in
                        Text(item.timestamp, format: Date.FormatStyle(date: .numeric, time: .standard))
                }
                .onDelete(perform: deleteItems)
            }
            .toolbar {
                ToolbarItem(placement: .navigationBarTrailing) {
                    EditButton()
                }
                ToolbarItem {
                    Button(action: addItem) {
                        Label("Add Item", systemImage: "plus")
                    }
                }
            }
        } detail: {
            DetailView(selection: selection)
        }
        .navigationSplitViewStyle(.balanced)
    }

    private func addItem() {
        withAnimation {
            let newItem = Item(timestamp: Date())
            modelContext.insert(newItem)
        }
    }

    private func deleteItems(offsets: IndexSet) {
        withAnimation {
            for index in offsets {
                modelContext.delete(items[index])
            }
        }
    }
}

#Preview {
    ContentView()
        .modelContainer(for: Item.self, inMemory: true)
}

and then I created DetailView.swift:

import SwiftUI
import SwiftData

struct DetailView: View {
    var selection: Item?
    
    var body: some View {
        if let item = selection {
            Text("Item at \(item.timestamp, format: Date.FormatStyle(date: .numeric, time: .standard))")
        } else {
            Text("nothing selected")
        }
    }
}

#Preview {
    return DetailView(selection: Item(timestamp: Date()))
        .modelContainer(for: Item.self, inMemory: true)
}

Item.swift is unchanged from how it was generate in the new project:

import Foundation
import SwiftData

@Model
final class Item {
    var timestamp: Date
    
    init(timestamp: Date) {
        self.timestamp = timestamp
    }
}

But when I add the parameter in the #Preview of DetailView, the compiler won't build the project:

/var/folders/9j/8r6qn35j5jjf0gm3crp6gynw0000gn/T/swift-generated-sources/@_swiftmacro_12navSplitView4Item5ModelfMc.swift:1:1 Type 'Item' does not conform to protocol 'PersistentModel'

What do I need to do to pass these @Model objects around as parameters?

1

2 Answers 2

5

The issue is that the Item object you create inside the #Preview macro doesn't belong to a ModelContext instance which generates the error.

To solve this we first need a separate ModelContainer for previews and use that container's model context to insert objects we use in our previews, here is a simple example.

#if DEBUG
@MainActor
let previewContainer: ModelContainer = {
    do {
        let container = try ModelContainer(for: Item.self, ModelConfiguration(inMemory: true))        
        container.mainContext.insert(Item.preview)
        
        return container
    } catch {
        fatalError("Failed to create preview container")
    }
}()
#endif

where Item.preview is a static property defined in Item

@Model
final class Item {
    // existing code...

    static let preview: Item = {
        Item(timestamp: .now)
    }()
}

And then the preview macro is changed to

#Preview {
    DetailView(selection: .preview)
}
Sign up to request clarification or add additional context in comments.

7 Comments

Except now the preview is crashing all of the time: Exception Type: EXC_BREAKPOINT (SIGTRAP) Exception Codes: 0x0000000000000001, 0x000000018cc95064 Termination Reason: SIGNAL 5 Trace/BPT trap: 5 Terminating Process: exc handler [7271] Triggered by Thread: 0 Kernel Triage: VM - (arg = 0x0) pmap_enter retried due to resource shortage VM - (arg = 0x0) pmap_enter retried due to resource shortage VM - (arg = 0x0) pmap_enter retried due to resource shortage VM - (arg = 0x0) pmap_enter retried due to resource shortage
And when I add previewContainer via .modelContainer(previewContainer) I get Compiling failed: main actor-isolated let 'previewContainer' can not be referenced from a non-isolated context. I believe that this is a bug with Xcode 15 beta 2, as Apple's proejct called SwiftDataFlashCardSample, which does this same trick, exhibits the same problem.
For the second issue use MainAcor.assumeIsolated { … } around the code inside the preview macro.
I have the same problem, i.e. #Preview crashes now with EXC_BREAKPOINT (SIGTRAP). How to build preview for views like DetailView(item: item)?
This happens to me also
|
3

What helped me as workaround: Above your preview code, create a wrapper view like this.

struct Wrapper: View {
    var body: some View {
        EditingView(item: .previewItem)
    }
}

In your preview, just call the Wrapper view and provide a container.

#Preview {
  Wrapper()                
  .modelContainer(previewContainer) // Inject preview content
}

1 Comment

That is a feasible solution, and I have used it even before SwiftData to simulate parent views holding \@State variables and passing bindings to the child view, which, in turn, is the view I wanted to develop and test. Inside the wrapper (I call it PreviewCHILDNAMEView), you can use \@Query statements and all other SwiftData magic, ultimately passing the retrieved \@Model object as \@Bindable to your actual View in development. As the .modelContainer is attached to the wrapper call in the preview, it is available with all in-memory down the view tree.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.