27

The code below works for sorting an array of strings if they are all lowercase or all uppercase but I want to ignore case when I sort. How could I do this? The following is an array of a custom class.

resultListArray.sort({ $0.fileName.compare($1.fileName) == NSComparisonResult.OrderedAscending })

Leo Dabus
  • 229,809
  • 59
  • 489
  • 571
Gary Dorman
  • 375
  • 1
  • 5
  • 16

5 Answers5

65

You can use String method localizedCompare()

update: Xcode 11.5 • Swift 5.2

let array = ["def","Ghi","Abc" ]

let sorted1 = array.sorted{$0.compare($1) == .orderedAscending} 
print(sorted1)  // ["Abc", "Ghi", "def"] this is case SENSITIVE!

let sorted2 = array.sorted{$0.localizedCompare($1) == .orderedAscending}
print(sorted2) // ["Abc", "def", "Ghi"]


// you can also use the String compare options parameter to give you more control when comparing your strings
let sorted3 = array.sorted{$0.compare($1, options: .caseInsensitive) == .orderedAscending }
print(sorted3)   // ["Abc", "def", "Ghi"]\n"

// which can be simplifyed using the string method caseInsensitiveCompare
let sorted4 = array.sorted{$0.caseInsensitiveCompare($1) == .orderedAscending}
print(sorted4) // ["Abc", "def", "Ghi"]\n"

// or localizedStandardCompare (case and diacritic insensitive)
// This method should be used whenever file names or other strings are presented in lists and tables where Finder-like sorting is appropriate. The exact sorting behavior of this method is different under different locales and may be changed in future releases. This method uses the current locale.
let array5 = ["Cafe B","Café C","Café A"]
let sorted5 = array5.sorted { $0.localizedStandardCompare($1) == .orderedAscending }
print(sorted5) // "["Café A", "Cafe B", "Café C"]\n"

You can also implement your own custom sort/sorted methods:

extension Collection where Element: StringProtocol {
    public func localizedSorted(_ result: ComparisonResult) -> [Element] {
        sorted { $0.localizedCompare($1) == result }
    }
    public func caseInsensitiveSorted(_ result: ComparisonResult) -> [Element] {
        sorted { $0.caseInsensitiveCompare($1) == result }
    }
    public func localizedCaseInsensitiveSorted(_ result: ComparisonResult) -> [Element] {
        sorted { $0.localizedCaseInsensitiveCompare($1) == result }
    }
    /// This method should be used whenever file names or other strings are presented in lists and tables where Finder-like sorting is appropriate. The exact sorting behavior of this method is different under different locales and may be changed in future releases. This method uses the current locale.
    public func localizedStandardSorted(_ result: ComparisonResult) -> [Element] {
        sorted { $0.localizedStandardCompare($1) == result }
    }
}

extension MutableCollection where Element: StringProtocol, Self: RandomAccessCollection {
    public mutating func localizedSort(_ result: ComparisonResult) {
        sort { $0.localizedCompare($1) == result }
    }
    public mutating func caseInsensitiveSort(_ result: ComparisonResult) {
        sort { $0.caseInsensitiveCompare($1) == result }
    }
    public mutating func localizedCaseInsensitiveSort(_ result: ComparisonResult) {
        sort { $0.localizedCaseInsensitiveCompare($1) == result }
    }
    /// This method should be used whenever file names or other strings are presented in lists and tables where Finder-like sorting is appropriate. The exact sorting behavior of this method is different under different locales and may be changed in future releases. This method uses the current locale.
    public mutating func localizedStandardSort(_ result: ComparisonResult) {
        sort { $0.localizedStandardCompare($1) == result }
    }
}

Usage:

var array = ["def","Ghi","Abc" ]
array.caseInsensitiveSort(.orderedAscending)
array    // ["Abc", "def", "Ghi"]


To sort a custom object by a string property we can pass a predicate to get the string from the element and use a keypath when calling this method:

extension Collection {
    /// This method should be used whenever file names or other strings are presented in lists and tables where Finder-like sorting is appropriate. The exact sorting behavior of this method is different under different locales and may be changed in future releases. This method uses the current locale.
    public func localizedStandardSorted<T: StringProtocol>(by predicate: (Element) -> T, ascending: Bool = true) -> [Element] {
        sorted { predicate($0).localizedStandardCompare(predicate($1)) == (ascending ? .orderedAscending : .orderedDescending) }
    }
}

extension MutableCollection where Self: RandomAccessCollection {
    /// This method should be used whenever file names or other strings are presented in lists and tables where Finder-like sorting is appropriate. The exact sorting behavior of this method is different under different locales and may be changed in future releases. This method uses the current locale.
    public mutating func localizedStandardSort<T: StringProtocol>(by predicate: (Element) -> T, ascending: Bool = true) {
        sort { predicate($0).localizedStandardCompare(predicate($1)) == (ascending ? .orderedAscending : .orderedDescending) }
    }
}

Usage:

struct File {
    let id: Int
    let fileName: String
}

var files: [File] = [.init(id: 2, fileName: "Steve"),
                     .init(id: 5, fileName: "Bruce"),
                     .init(id: 3, fileName: "alan")]

let sorted = files.localizedStandardSorted(by: \.fileName)
print(sorted)  // [File(id: 3, fileName: "alan"), File(id: 5, fileName: "Bruce"), File(id: 2, fileName: "Steve")]

files.localizedStandardSort(by: \.fileName)
print(files)  // [File(id: 3, fileName: "alan"), File(id: 5, fileName: "Bruce"), File(id: 2, fileName: "Steve")]

edit/update:

iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0 or later

You can use the new generic structure KeypathComparator

let sorted = files.sorted(using: KeyPathComparator(\.fileName, comparator: .localizedStandard))
print(sorted)  // [File(id: 3, fileName: "alan"), File(id: 5, fileName: "Bruce"), File(id: 2, fileName: "Steve")]
let reversed = files.sorted(using: KeyPathComparator(\.fileName, comparator: .localizedStandard, order: .reverse))
print(reversed)  // [File(id: 2, fileName: "Steve"), File(id: 5, fileName: "Bruce"), File(id: 3, fileName: "alan")]

files.sort(using: KeyPathComparator(\.fileName, comparator: .localizedStandard))
print(files)  // [File(id: 3, fileName: "alan"), File(id: 5, fileName: "Bruce"), File(id: 2, fileName: "Steve")]

For simple types you need to use self as the KeyPath

var array = ["def","Ghi","Abc"]
let sorted = array.sorted(using: KeyPathComparator(\.self, comparator: .localizedStandard))

Or mutating the original

array.sort(using: KeyPathComparator(\.self, comparator: .localizedStandard))
Leo Dabus
  • 229,809
  • 59
  • 489
  • 571
17

You can convert the String to lowercase and then compare it:

array.sort{ $0.lowercaseString < $1.lowercaseString }
Qbyte
  • 12,753
  • 4
  • 41
  • 57
3

This is the method that should be used and is intended for this purpose:

public func caseInsensitiveCompare(aString: String) -> NSComparisonResult

In your case:

resultListArray.sort({ $0.fileName.caseInsensitiveCompare($1.fileName) == NSComparisonResult.OrderedAscending })
Abhishek Bedi
  • 5,205
  • 2
  • 36
  • 62
Lubos
  • 1,169
  • 10
  • 19
2
var items = ["a","A","b","B","c","C","d","D"] 
items = items.sorted(by: { (item1, item2) -> Bool in
     return item1.localizedCompare(item2) == .orderedAscending
})
Brad Larson
  • 170,088
  • 45
  • 397
  • 571
Ly Hor Sin
  • 91
  • 1
  • 3
0

Here’s an answer about overriding compareTo in Java to change how sort orders things. What happens if you convert the strings to uppercase, then compare them?

Community
  • 1
  • 1
Davislor
  • 14,674
  • 2
  • 34
  • 49
  • 1
    I'm programming in Swift. Normally I could just convert the strings to uppercase or lowercase but that isn't an option this time. – Gary Dorman Sep 06 '15 at 01:20
  • Oh, my apologies. I misread the tag. See this question, then: https://stackoverflow.com/questions/31871395/swift-2-iterating-and-upper-lower-case-some-characters – Davislor Sep 06 '15 at 01:23