11

I'm trying to fetch some results with the new #Predicate macro. The problem appears when I try to use an enum property inside the predicate. Does anyone know if it can be done or if this is a bug?

XCode 15 beta 4

Fetch 1:

let predicate = #Predicate<ModelA> { $0.type == ModelA.ModelAType.a }

Fetch 2:

let predicate = #Predicate<ModelA> { $0.type.rawValue == ModelA.ModelAType.a.rawValue }

Model:

@Model final class ModelA: Codable {
    @Attribute(.unique) var id: Int64 = 0
    enum ModelAType: Int, CaseIterable, Codable { case a = 1, b }
    var type: ModelAType = ModelAType.a
}

Errors:

Type 'ModelA.Type' cannot conform to 'StandardPredicateExpression'
Type 'ModelA.Type' cannot conform to 'PredicateExpression'
Cannot infer key path type from context; consider explicitly specifying a root type

2 Answers 2

5

As of Xcode 15.2 this doesn't seem to work at all if you want to use an enum in a predicate, a workaround is to store the raw value of the enum directly and use a computed property to convert between the stored value and the enum type.


What worked for me in this situation was to define a constant for the raw value and use that constant in the predicate

let aValue = ModelA.ModelAType.a.rawValue
let predicate = #Predicate<ModelA> { $0.type.rawValue == aValue }

I am not sure why this is needed but hopefully it’s a beta thing that will change soon

Sign up to request clarification or add additional context in comments.

6 Comments

doesn't seem to work on XCode 15.1 beta 3 and simulator 17.2
@AndroidGecko Seems we have a regression, I can't make it work either even though I tried several different variants.
Also doesnt work for me with Xcode 15.2 and Simulator 17.2
For me storing the actual enum case in a constant and then using worked fine (without the .rawValue)
This seems to get be past the compile error however when fetching it seems to lead to a different one SwiftData/DataUtilities.swift:65: Fatal error: Couldn't find \enum.rawValue
|
3

I found a workaround.

  1. Create a property type of Int for represent the rawValue enum.
  2. Create a getter/setter property to represent/resolver your enum.
  3. In your #Predicate, use Int property to compare.

Follow the solution:

enum PlanStatus: Int, Codable {
    case working, standby, inactive
}

@Model
final class Plan: Identifiable, Hashable {
    
    // MARK: - Public Properties
    
    @Attribute(.unique)
    private(set) var id: UUID
    
    private(set) var createdAt: Date
    
    var title: String
    var start: Date
    var end: Date
    
    var status: PlanStatus {
        get { PlanStatus(rawValue: _status)! }
        set { _status = newValue.rawValue }
    }
    
    // MARK: - Private Properties
    
    private var _status: PlanStatus.RawValue
    
    // MARK: - Life Cycle
    
    init(
        _ title: String,
        start: Date,
        end: Date,
        status: PlanStatus = .standby
    ) {
        id = UUID()
        createdAt = .now
        
        self.title = title
        self.start = start
        self.end = end
        
        _status = status.rawValue
    }
    
}

In your predicate use:

let sortBy = [SortDescriptor<Plan>(\.createdAt, order: .reverse)]
let state = PlanStatus.inactive
let predicate = #Predicate<Plan> { $0._status == status.rawValue }
let descriptor = Plan.fetchDescriptor(predicate: predicate, sortBy: sortBy)

1 Comment

you made _status private and then you use it in the descriptor. this might be a typo since that code couldn't build. other than that, the solution as a whole works.

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.