SwiftData #Predicate case insensitive String comparison

SwiftData #Predicate performs $0.string1 == string2 as case sensitive. Searching for Taylor Swift returns results, but not for taylor swift.

In Core Data we did this with ==[c]

How do we do this in SwiftData?

Post not yet marked as solved Up vote post of OsmanBalci Down vote post of OsmanBalci
1.6k views

Replies

Same problem. I couldn't find any solution. The macro uses PredicateExpressions enumerations to build the predicate. The enumeration includes a lot of methods but none seems to work for case insensitive searches. I hope they fix this soon or SwiftData becomes completely useless for most cases.

  • EDIT: see solution in comment on post

    Same here, filed a feedback around this. As far as I'm concerned, not being able to do case insensitive searches is a deal breaker

Add a Comment

I've played around with this, trying a lot of solutions, but I might have found a way to do case insensitive compare. In the SwiftData SampleTrips sample project I changed

        let filteredList = try? trip.bucketList.filter(#Predicate { item in
            item.title.contains(searchText) && tripName == item.trip?.name
        })

to:

        let filteredList = try? trip.bucketList.filter(#Predicate { item in
          return item.title.localizedLowercase.contains(searchText.localizedLowercase) && tripName == item.trip?.name
        })

This seemed to work. It seems the macro has support for localizedLowercase but not for lowerCased()

Hope that helps. I'm a lot more excited about SwiftData now.

I followed your suggestion:

    musicAlbumPredicate = #Predicate<MusicAlbum> {
        $0.artistName.localizedLowercase.contains(searchQuery.localizedLowercase)
    }

It produced the following error messages:

SwiftData/DataUtilities.swift:93: Fatal error: Couldn't find \MusicAlbum.artistName.localizedLowercase on MusicAlbum with fields [("artistName", \MusicAlbum.artistName, nil, nil), ("albumName", \MusicAlbum.albumName, nil, nil), ("songName", \MusicAlbum.songName, nil, nil), ("genre", \MusicAlbum.genre, nil, nil), ("rating", \MusicAlbum.rating, nil, nil), ("releaseDate", \MusicAlbum.releaseDate, nil, nil), ("coverPhotoFullFilename", \MusicAlbum.coverPhotoFullFilename, nil, nil)]

Just use the localizedStandardContains(_:) function in your predicate.

let predicate = #Predicate<Band> { $0.name.localizedStandardContains(userInput) }

This is the most appropriate method for doing user-level string searches, similar to how searches are done generally in the system. The search is locale-aware, case and diacritic insensitive. The exact list of search options applied may change over time.

https://developer.apple.com/documentation/swift/stringprotocol/localizedstandardcontains(_:)

  • localizedStandardContains(_:) solves the problem! Many thanks!!!

Add a Comment

I'm sorry, but this isn't right. localizedStandardContains(_:) provides CONTAINS semantics, not EQUALS semantics. The original poster (and I) want a case-insensitive test for equality. Using localizedStandardCompare(_:) == .orderedSame is not supported by the current (Xcode 15.0 beta 7) version of SwiftData, and the alternative suggested by Xcode's error messages (localizedCompare(_:)) is case-sensitive.

The only thing I can think of at this point (not tested, by the way) is

    $0. attribute.localizedStandardContains(str) && str.localizedStandardContains($0.attribute)

which is ridiculous. FB13051739

SwiftData is not ready for prime time until this gets addressed!

Predicates only provide support for caseInsensitiveCompare(_:) when making case-insensitive string comparisons. This function returns a ComparisonResult, which has three cases: orderedAscending, orderedSame, and orderedDescending.

To create a predicate that does a case-insensitive string comparison, you'll first need to create a constant defining the expected return value. This is because referring to an enum case using a keypath (e.g. ComparisonResult.orderedSame) is not supported in predicates.

let orderedSame = ComparisonResult.orderedSame

let predicate = #Predicate<Person> { person in
    person.name.caseInsensitiveCompare(name) == orderedSame
}