To suppress consecutive Swift enum cases while ignoring associated values, compare a separate case key and retain the first complete value in each run. This local Mac example preserves order and allows a case to appear again after another case.

Define exactly what counts as a duplicate

Suppose a desktop graphics preview produces a synthetic sequence of steps: idle, drawing a named shape, and showing a named preview. You want a compact list of case transitions. Two consecutive drawing steps count as duplicates even when their shape names differ.

Swift enums can carry different associated values for each case. Those values are part of the enum value, so choosing to ignore them needs an explicit comparison rule. The Swift language guide explains this distinction in Enumerations.

For this task, use the following contract:

  • Keep the first input value.
  • Suppress each later value whose case matches the preceding run’s case.
  • Preserve the complete associated value of every retained element.
  • Keep a case again when it follows a different case.
  • Preserve input order; an empty input produces an empty result.

Here is the intended behavior for an illustrative input. Every name is synthetic.

Input value Decision Reason
.idle Keep First value
.drawing("Circle") Keep Case changed
.drawing("Square") Suppress Still drawing
.ready("Preview A") Keep Case changed
.ready("Preview B") Suppress Still ready
.drawing("Triangle") Keep Drawing follows ready
.idle Keep Case changed
.idle Suppress Still idle

The retained drawing values are therefore "Circle" and "Triangle". The "Square" payload is deliberately excluded. If the desktop view must display every updated shape name, this contract would discard changes that view needs; choose payload-sensitive comparison for that consumer.

Run a complete local Swift example on your Mac

Use a Mac with a working Swift development environment. Swift.org recommends Xcode for Apple-platform development and documents a command-line executable workflow using Swift Package Manager. Follow Install Swift — macOS if you need to prepare the tools.

In Terminal, create a small executable package in a new working directory:

mkdir EnumCaseDemo
cd EnumCaseDemo
swift package init --name EnumCaseDemo --type executable

Replace the generated executable source file’s contents with the following complete program. The program uses Swift standard-library types and requires no imports or package dependencies. Its input and output remain in memory; it prints the retained synthetic steps.

enum PreviewStep: Equatable {
    case idle
    case drawing(String)
    case ready(String)

    enum Kind: Equatable {
        case idle
        case drawing
        case ready
    }

    var kind: Kind {
        switch self {
        case .idle:
            return .idle
        case .drawing(_):
            return .drawing
        case .ready(_):
            return .ready
        }
    }

    var label: String {
        switch self {
        case .idle:
            return "idle"
        case .drawing(let shape):
            return "drawing: \(shape)"
        case .ready(let name):
            return "ready: \(name)"
        }
    }
}

func suppressConsecutiveCases(
    _ steps: [PreviewStep]
) -> [PreviewStep] {
    var result: [PreviewStep] = []
    var previousKind: PreviewStep.Kind? = nil

    for step in steps {
        let currentKind = step.kind
        if let previous = previousKind,
           previous == currentKind {
            continue
        }

        result.append(step)
        previousKind = currentKind
    }

    return result
}

let input: [PreviewStep] = [
    .idle,
    .drawing("Circle"),
    .drawing("Square"),
    .ready("Preview A"),
    .ready("Preview B"),
    .drawing("Triangle"),
    .idle,
    .idle
]

let expected: [PreviewStep] = [
    .idle,
    .drawing("Circle"),
    .ready("Preview A"),
    .drawing("Triangle"),
    .idle
]

let output = suppressConsecutiveCases(input)

precondition(output == expected)
precondition(suppressConsecutiveCases([]).isEmpty)
precondition(
    suppressConsecutiveCases([
        .drawing("Circle"), .drawing("Square")
    ]) == [.drawing("Circle")]
)
precondition(
    suppressConsecutiveCases([
        .idle, .drawing("Circle"), .idle
    ]) == [.idle, .drawing("Circle"), .idle]
)

for step in output {
    print(step.label)
}

Run the package from its directory:

swift run EnumCaseDemo

The expected program output, following any build messages, is:

idle
drawing: Circle
ready: Preview A
drawing: Triangle
idle

These examples were inspected against the retrieved documentation; they were not executed here because the research environment has no Swift executable. The included preconditions check the expected retained payloads, empty input, consecutive differing payloads, and a case returning after a transition.

Why a separate case key works

The kind property converts every full enum value into a small, payload-free key. Both .drawing("Circle") and .drawing("Square") produce the key .drawing. The filtering function compares those keys while appending the original step, so retained associated values stay intact.

The function needs only one remembered key. At the start, previousKind is nil, allowing the first element through. After retaining an element, it remembers that element’s kind. Suppressed elements have the same kind, so updating the remembered key for them would make no difference.

This implementation walks the input once. For this fixed enum and its constant-size keys, it performs one case-key comparison per subsequent element and uses constant auxiliary state, plus the returned array. Swift’s Sequence documentation describes sequential iteration and the usual linear traversal model.

The full PreviewStep also conforms to Equatable, allowing the preconditions to verify complete results, including retained strings. Swift supports synthesized equality for eligible enums whose associated values conform to Equatable; see Protocols. Keeping case comparison in Kind gives this filtering operation its own explicit rule without redefining equality throughout the program.

The exhaustive switch in kind also provides a useful maintenance checkpoint. When you add a new case, add its corresponding key and mapping. Avoid a catch-all mapping that accidentally groups a new case with an existing one. Swift documents exhaustive enum switching in Enumerations.

Use the same comparison with Combine

If your Mac code already uses a Combine publisher, Apple provides removeDuplicates(by:) for a custom comparison. Its predicate returns true when the second value should count as a duplicate. Apple explicitly supports this operator when the desired comparison differs from the output type’s ordinary equality. It is available on macOS 10.15 and later. See removeDuplicates(by:).

The following is an extension fragment, not a standalone program. Append it to the complete example above on a Mac with Combine available. It reuses the existing PreviewStep definition and input array:

import Combine

let subscription = input.publisher
    .removeDuplicates { previous, current in
        previous.kind == current.kind
    }
    .sink { step in
        print("Combine: \(step.label)")
    }

This prints the same five retained steps with a "Combine: " prefix. The predicate compares case keys, and the publisher passes through the original retained enum values. Apple’s own operator example follows the same principle by comparing one property while ignoring another. removeDuplicates(by:)

Keep the returned subscription alive for the required subscription lifetime. Apple documents that deallocating the value returned by sink(receiveValue:) tears down the subscription. This overload applies to publishers whose failure type is Never, as in Apple’s sequence-publisher examples. sink(receiveValue:)

Check the boundaries before adapting the code

Use the array function when you already have a finite local list. Use the Combine operator when the values already arrive through a publisher. Both versions here express the same case-transition contract.

Requirement Appropriate comparison or approach
Keep the first complete value in each case run Compare kind, as shown
Keep changes to associated strings Compare full values using ordinary equality
Keep the last complete value in each run Buffer a pending value and flush it on a case transition and at the end
Keep a returning case after another case Compare consecutive runs rather than maintaining a global seen-case list
Filter separate arrays independently Call the function separately; each call starts with empty comparison state

Keeping the last payload requires a different algorithm because this program appends the first value immediately. Likewise, splitting one logical sequence into separate arrays resets comparison state: a drawing value at the end of one batch and another at the start of the next will both survive separate calls. If batches must behave as one sequence, preserve the previous key across batch boundaries.

Foundation supplies collections and other base functionality for Apple-platform apps, but this calculation is implemented entirely with Swift types. The publisher variant specifically requires Combine; adding import Foundation does not import that operator. Apple documents these frameworks separately in Foundation and removeDuplicates(by:).

The synchronous array function also needs no concurrency machinery: its comparison state belongs to each invocation. If you later introduce shared desktop presentation state, define that state’s isolation separately. Apple documents MainActor as a singleton actor whose executor is equivalent to the main dispatch queue; duplicate suppression itself does not select an actor. MainActor

For the local task shown here, the decisive choice is which payload survives a run. Once you choose the first payload, a separate case key makes the behavior straightforward to read and verify: retain the original value on a case transition, suppress matching consecutive keys, and let returning cases through.

Sources