How to Sort an Array of Custom Objects by Property in Swift

How to Sort an Array of Custom Objects by Property in Swift

Written by

in

Quick answer: Declare the array with its element type, then call sorted(by:) with a closure that compares the property: images.sorted { $0.fileID < $1.fileID }. Use sort(by:) instead if you want to reorder the existing variable in place.

Why this happens

The compiler can only sort an array when it knows what type the elements are. In the original code, var images : Array = [] uses the generic Array type without its element type. Modern Swift rejects that with a generic-argument error, and if the array becomes [Any], the closure cannot see fileID at all. You may see Value of type 'Any' has no member 'fileID' when you try to compare elements.

An empty array literal also needs a type from somewhere. If you write var images = [] and only append objects later, Swift still needs an explicit element type at the declaration. Declaring [ImageFile] fixes both problems.

There is also a common mix-up between two methods. sorted(by:) returns a new array and leaves the original alone. sort(by:) reorders the array in place and requires a var. Both take a closure that returns true when the first element should come before the second.

How to fix it

1. Declare the array as [ImageFile]

Start with a concrete element type. That gives the compiler the property names available on each element.

struct ImageFile {
    var fileName: String
    var fileID: Int
}

var images: [ImageFile] = [
    ImageFile(fileName: "image1.png", fileID: 101),
    ImageFile(fileName: "image2.png", fileID: 202)
]

If ImageFile is a class, the array type is still [ImageFile]. Sorting moves references around; it does not change the objects themselves.

2. Sort ascending with sorted(by:)

Use < for ascending order:

let ascending = images.sorted { $0.fileID < $1.fileID }

ascending is a new array. images keeps its original order. The trailing closure is the by: argument, so images.sorted(by: { $0.fileID < $1.fileID }) is the same call written out.

3. Sort descending

Use > instead:

let descending = images.sorted { $0.fileID > $1.fileID }

Do not use <= or >= in the closure. A sort comparator must be a strict ordering; equal elements must return false in both directions.

4. Sort the existing array in place

If you want to change images itself, call sort(by:):

images.sort { $0.fileID < $1.fileID }

This requires images to be declared with var. On a let constant you will get cannot use mutating member on immutable value: 'images' is a 'let' constant.

5. Use a named comparator for reuse

If the same order is needed in several places, pull the closure out:

func sorterForFileIDASC(_ a: ImageFile, _ b: ImageFile) -> Bool {
    a.fileID < b.fileID
}

let sorted = images.sorted(by: sorterForFileIDASC)

6. Add a tie-breaker

When two objects have the same fileID, the order between them is not defined by that comparison alone. Compare a second property to make the result predictable:

let sorted = images.sorted {
    if $0.fileID != $1.fileID {
        return $0.fileID < $1.fileID
    }
    return $0.fileName < $1.fileName
}

For properties that are all Comparable, a tuple comparison is shorter:

let sorted = images.sorted {
    ($0.fileID, $0.fileName) < ($1.fileID, $1.fileName)
}

7. Give the type a natural order with Comparable

If sorting by fileID is the normal order for ImageFile, conform to Comparable:

struct ImageFile: Comparable {
    var fileName: String
    var fileID: Int

    static func < (lhs: ImageFile, rhs: ImageFile) -> Bool {
        lhs.fileID < rhs.fileID
    }
}

let sorted = images.sorted()

Only do this when the type has one obvious default order. Otherwise keep the closure at the call site so the intent is visible.

8. Handle an optional property

If fileID is Int?, unwrap before comparing:

let sorted = images.sorted {
    ($0.fileID ?? 0) < ($1.fileID ?? 0)
}

Choose a fallback that matches your data. Do not force-unwrap inside the comparator; one nil value will crash the sort.

How to prevent it

  • Declare arrays as [ImageFile], not bare Array or [Any].
  • Use < or > in comparators, never <= or >=.
  • Use sorted(by:) for a new array and sort(by:) for in-place changes.
  • Add a tie-breaker when equal property values should have a stable, predictable order.
  • Extract a named comparator or add Comparable when the same sort is repeated.

FAQ

Does sorted(by:) modify the original array?

No. sorted(by:) returns a new sorted array. To reorder the existing array, use sort(by:) on a var.

Can I sort by a String property?

Yes. String is Comparable, so images.sorted { $0.fileName < $1.fileName } works. For a case-insensitive sort, compare with localizedCaseInsensitiveCompare and check for .orderedAscending.

Why does a comparator with <= cause problems?

A sort comparator must return true only when the first element strictly belongs before the second. Using <= makes equal elements compare as both before and after each other, which can produce an invalid order or unexpected results.

Sources & credits

Parts of this guide are adapted from content licensed under CC BY-SA 4.0; this article is shared under the same license.