6

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?

HangarRash
  • 7,314
  • 5
  • 5
  • 32
Syd Polk
  • 73
  • 1
  • 5

1 Answers1

3

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)
}
Joakim Danielson
  • 43,251
  • 5
  • 22
  • 52
  • 1
    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 ``` – Syd Polk Jul 06 '23 at 05:25
  • 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. – Syd Polk Jul 06 '23 at 05:31
  • 1
    For the second issue use MainAcor.assumeIsolated { … } around the code inside the preview macro. – Joakim Danielson Jul 06 '23 at 06:02
  • I have the same problem, i.e. `#Preview` crashes now with `EXC_BREAKPOINT (SIGTRAP)`. How to build preview for views like `DetailView(item: item)`? – blu-Fox Jul 16 '23 at 18:08