swiftdata

SwiftData Query to return true if element in Model is contained within an array


I have a SwiftData Model like this:

@Model class LogInfo {
    var tagValue: String
    ...
}

I want to display a list of all LogInfo objects for which the tagValue is contained within an array. The SwiftUI view looks like this:

struct LogListView: View {

    @Query
    private var logs: [LogInfo]
    
    init(tagsToFilter: [LogTag]) {
        _logs = Query(filter: #Predicate<LogInfo> { logInfo in
            if tagsToFilter.isEmpty || tagsToFilter.contains(where: { tag in
                tag.rawValue == logInfo.tagRawValue
            }) {
                true
            } else {
                false
            }
        })
    }

The tagsToFilter argument will be all the tags selected by the user (multi selection). The LogTag object is an enum, declared this way:

public enum LogTag: String, Codable, CaseIterable, Hashable, Identifiable

Because the query won't let me check against the enum directly without crashing, I've "doubled" the information with the rawValue. So I'm creating the LogListView like this: LogListView(tagsToFilter: [.case1, .case2]). I want the list to display every LogInfo object which have either tag's raw value "case1" or "case2" in this example.

The Query works when I have an empty list, or if I remove the query altogether. But the predicate doesn't filter anything. I tried a simpler predicate, like:

#Predicate<LogInfo> { logInfo in 
    tagsToFilter.contains(where: { tag in 
        tag.rawValue == logInfo.tagRawValue
    }
}

but this returns an empty list.

Am I holding it wrong, or is another SwiftData's limitation?


Solution

  • Try this approach using a separate Predicate and tagStrings instead of trying to use a dynamic Query with everything inside it.

    Example code.

    
    struct LogListView: View {
        @Environment(\.modelContext) private var modelContext
        @Query private var logs: [LogInfo]
    
        init(tagsToFilter: [LogTag]) {
            let tagStrings = tagsToFilter.map { $0.rawValue }
            let predicate = #Predicate<LogInfo> { log in
                tagStrings.contains(log.tagValue)
            }
            _logs = Query(filter: predicate)
        }
    
        var body: some View {
            List(logs) { log in
                Text(log.name) // <-- adjust as required
            }
        }
    }
    
    enum LogTag: String, Codable, CaseIterable {
        case case1, case2, case3, case4 
    }