Skip to content

@Equatable incompatible with SwiftData @Query #33

@christopherwxyz

Description

@christopherwxyz

Issue

The @Equatable macro is incompatible with SwiftData's @Query property wrapper, preventing its use in views that need to fetch and observe persistent data.

Reproduction

import Equatable
import SwiftData
import SwiftUI

@Model
final class Item {
    var name: String
    var timestamp: Date

    init(name: String, timestamp: Date = .now) {
        self.name = name
        self.timestamp = timestamp
    }
}

@Equatable(isolation: .main)
struct ItemListView: View {
    @Query private var items: [Item]
    let filterText: String

    var body: some View {
        List(items) { item in
            Text(item.name)
        }
    }
}

Error

When the view is rendered:

Set a .modelContext in view's environment to use Query

This occurs even when modelContext is properly provided via .environment(\.modelContext, modelContext) or .modelContainer().

Root Cause

The @Equatable macro generates a custom init() that runs during view initialization. However, SwiftUI's @Query property wrapper depends on EnvironmentValues.modelContext being available during initialization to set up its fetch request and model observation.

The initialization timing conflict occurs because:

  1. @Equatable generates custom initialization code that executes first
  2. SwiftUI's environment injection happens after the view struct is initialized
  3. @Query attempts to access modelContext from the environment during its property wrapper initialization
  4. The environment hasn't been injected yet, causing the runtime error

This is similar to other SwiftUI property wrappers that depend on environment values (like @FetchRequest in Core Data), where custom initialization can break the environment dependency chain.

Expected Behavior

Ideally, @Equatable should be compatible with all SwiftUI property wrappers, including those that depend on environment values like @Query, @FetchRequest, and custom @Environment properties.

Current Workaround

Views that use @Query cannot use @Equatable. Instead, child components that don't use @Query can be optimized:

// Parent view - cannot use @Equatable
struct ItemListView: View {
    @Query private var items: [Item]
    let filterText: String

    var body: some View {
        List(items) { item in
            ItemRow(item: item)  // This can use @Equatable
        }
    }
}

// Child component - can use @Equatable
@Equatable(isolation: .main)
struct ItemRow: View {
    let item: Item

    var body: some View {
        Text(item.name)
    }
}

Related Apple Documentation

Environment

  • Swift 6.2
  • SwiftUI (iOS 17.0+)
  • SwiftData (iOS 17.0+)
  • Equatable v1.2.0

Potential Solution

The macro might need to detect SwiftUI property wrappers that depend on environment values and either:

  1. Skip custom init generation for views with environment-dependent property wrappers
  2. Generate init code that preserves environment injection timing
  3. Provide a compilation error with a helpful message when incompatible property wrappers are detected

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions