Search code examples
iosarraysswiftpredicateswiftdata

Swift Predicate not working in Swift Data for String Array


In Swift Data I have a basic model

@Model class MovieSD {
 
    @Attribute(.unique) var title: String
    var genre: [String] = [String]()
    
    init(title: String) {
        self.title = title
    }
    
}

I am trying to create predicates when fetching the data. Creating a predicate to search by title works as expected

let movieTitle = #Predicate<MovieSD> { movie in
    movie.title.contains("filterstring")
}

But when attempting to do the same for the String array

let movieGenre = #Predicate<MovieSD> { movie in
    movie.genre.contains("filterstring")
}

Results in a crash with EXC_BAD_ACCESS

Similar approaches produce a different error and point to the likely issue.

let movieGenre2 = #Predicate<MovieSD> { movie in
    if movie.genre.contains(where: { $0 == "filterstring" }) {
         return true
    } else {
        return false
    }
}

Results in a crash with the error:

error: SQLCore dispatchRequest: exception handling request: <NSSQLFetchRequestContext: 0x281a96840> , Can't have a non-relationship collection element in a subquerySUBQUERY(genre, $$_local_1, $$_local_1 == "SciFi") with userInfo of (null)

or alternatively largely the same error for:

let movieGenre3 = #Predicate<MovieSD> { movie in
    movie.genre.filter { genre in
        return genre == "filterstring"
    }.count > 0
}

It seems that Swift Predicate should be able to make condition statements that would rely on SUBQUERY with NSPredicate. But in this case it doesn't seem too be working.

Naturally, I can use similar to the above to filter the array that is returned. But this seems inefficient. And it seems it should be possible to filter. Also, I want to avoid approaches that make the genres array into a @model Genre class.

Any help showing how to filter a String array with a predicate with Swift Data would be appreciated.


Solution

  • If it suits your application you could store the genre in a different table and query on that.

    @Model class Genre {
        @Attribute(.unique) var name: String
        
        init(name: String) {
            self.name = name
        }
        
    }
    @Model class MovieSD {
        @Attribute(.unique) var title: String
        var genre = [Genre]()
        
        init(title: String) {
            self.title = title
        }
        
    }
    

    This article can help explain why this happens.

    if you use collections of value types, e.g. [String], SwiftData will save that directly inside a single property too. Right now it’s encoded as binary property list data, which means you can’t use the contents of your array in a predicate.