Reputation: 133
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?
Upvotes: 10
Views: 3123
Reputation: 184
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
}
Upvotes: 3
Reputation: 52043
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)
}
Upvotes: 5