DiscreteFormatStyle

A format style that transforms a continuous input into a discrete output and provides information about its discretization boundaries.

iOS
16.4+
macOS
13.3+
tvOS
16.4+
watchOS
9.4+
protocol DiscreteFormatStyle<FormatInput, FormatOutput> : FormatStyle
Browse conforming types

Use this protocol to keep displays up to date if input changes continuously, or to iterate over all possible outputs of a FormatStyle by obtaining the next discrete input in either direction from discreteInput(before:) or discreteInput(after:).

Ordering of Inputs

The ordering over FormatInput defined by discreteInput(before:) / discreteInput(after:) must be consistent between the two functions. If FormatInput conforms to the Comparable protocol, the format style’s ordering should be consistent with the canonical ordering defined via the Comparable conformance, i.e. it should hold that discreteInput(before: x)! < x < discreteInput(after: x)! where discrete inputs are not nil.

Stepping through Discrete Input/Output Pairs

One use case of this protocol is enumerating all discrete inputs of a format style and their respective outputs.

While the discreteInput(before:) and discreteInput(after:) functions are the right tool for that, they do not give a guarantee that their respective return values actually produce an output that is different from the output produced by formatting the input value used when calling discreteInput(before:) / discreteInput(after:), they only provide a value that produces a different output for most inputs. E.g. when formatting a floating point value as an integer, we can get the next discrete input after x by calculating floor(x + 1). However, when rounding toward zero, the whole interval (-1;1) formats as zero. It would be ok for a discrete format style to ignore that edge case and return 0 for the discreteInput(after:) a negative value greater than -1. Therefore, to enumerate all discrete input/output pairs, adjacent outputs must be deduplicated in order to guarantee no adjacent outputs are the same.

The following example produces all discrete input/output pairs for inputs in a given range making sure adjacent outputs are unequal:

extension DiscreteFormatStyle
    where FormatInput : Comparable, FormatOutput : Equatable
{
        func enumerated(
        in range: ClosedRange<FormatInput>
    ) -> [(input: FormatInput, output: FormatOutput)] {
        var input = range.lowerBound
        var output = format(input)

        var pairs = [(input: FormatInput, output: FormatOutput)]()
        pairs.append((input, output))

        // get the next discretization bound
        while let nextInput = discreteInput(after: input),
              // check that it is still in the requested `range`
              nextInput <= range.upperBound {
            // get the respective formatted output
            let nextOutput = format(nextInput)
            // deduplicate based on the formatted output
            if nextOutput != output {
                pairs.append((nextInput, nextOutput))
            }
                input = nextInput
            output = nextOutput
        }

        return pairs
    }
}

Imperfect Discretization Boundaries

In some scenarios, a format style cannot provide precise discretization boundaries in a performant manner. In those cases it must override input(before:) and input(after:) to reflect that. For any discretization boundary x returned by either discreteInput(before:) or discreteInput(after:) based on the original input y, all values representable in the FormatInputstrictly between x and the return value of input(after: x) or input(before: x), respectively, are not guaranteed to produce the same formatted output as y.

The following schematic shows an overview of the guarantees given by the protocol:

xB = discreteInput(before: y)       y      xA = discreteInput(after: y)
      |                             |                             |
<-----+---+-------------------------+-------------------------+---+--->
          |                                                   |
 zB = input(after: xB)                          zA = input(before: xA)
  • the formatted output for everything in zB...zA (including bounds) is guaranteed to be equal to format(y)

  • the formatted output for xB and lower is most likely different from format(y)

  • the formatted output for xA and higher is most likely different from format(y)

  • the formatted output between xB and zB, as well as zA and xA (excluding bounds) cannot be predicted