Skip to content

Instantly share code, notes, and snippets.

@ole
Created September 9, 2023 13:33
Show Gist options
  • Save ole/dc26557165fbccbf80587504db29ed4c to your computer and use it in GitHub Desktop.
Save ole/dc26557165fbccbf80587504db29ed4c to your computer and use it in GitHub Desktop.
An "extension" of FloatingPointFormatStyle that adds a `minusSign` API to customize the character(s) used as the minus sign. You could add additional extensions by following the same pattern. This demonstrates that "extending" the built-in format styles requires a lot of boilerplate because you have to replicate their APIs in your own type.
import Foundation
/// An "extension" of FloatingPointFormatStyle that adds a `minusSign` API to customize
/// the character(s) used as the minus sign.
///
/// You could add additional extensions by following the same pattern.
///
/// All other APIs are copied from FloatingPointFormatStyle and forward to it for their
/// implementation. This isn’t a full replica of the FloatingPointFormatStyle API, though,
/// because it’s only intended as a proof of concept.
@available(macOS 12.0, *)
public struct ExtensibleFloatingPointFormatStyle<Value>
where Value : BinaryFloatingPoint
{
public var base: FloatingPointFormatStyle<Value>
public var minusSign: Optional<String>
public init(locale: Locale = .autoupdatingCurrent) {
self.base = .init(locale: locale)
self.minusSign = nil
}
private init(base: FloatingPointFormatStyle<Value>, minusSign: String?) {
self.base = base
self.minusSign = minusSign
}
// MARK: - New APIs
public func minusSign(_ sign: String) -> Self {
Self(base: base, minusSign: sign)
}
// MARK: - Replicating FloatingPointFormatStyle APIs
public func locale(_ locale: Locale) -> Self {
Self(base: base.locale(locale), minusSign: minusSign)
}
public func grouping(_ group: FloatingPointFormatStyle<Value>.Configuration.Grouping) -> Self {
Self(base: base.grouping(group), minusSign: minusSign)
}
public func precision(_ p: FloatingPointFormatStyle<Value>.Configuration.Precision) -> Self {
Self(base: base.precision(p), minusSign: minusSign)
}
public func sign(strategy: FloatingPointFormatStyle<Value>.Configuration.SignDisplayStrategy) -> Self {
Self(base: base.sign(strategy: strategy), minusSign: minusSign)
}
public func decimalSeparator(strategy: FloatingPointFormatStyle<Value>.Configuration.DecimalSeparatorDisplayStrategy) -> Self {
Self(base: base.decimalSeparator(strategy: strategy), minusSign: minusSign)
}
public func rounded(rule: FloatingPointFormatStyle<Value>.Configuration.RoundingRule = .toNearestOrEven, increment: Double? = nil) -> Self {
Self(base: base.rounded(rule: rule, increment: increment), minusSign: minusSign)
}
public func scale(_ multiplicand: Double) -> Self {
Self(base: base.scale(multiplicand), minusSign: minusSign)
}
public func notation(_ notation: FloatingPointFormatStyle<Value>.Configuration.Notation) -> Self {
Self(base: base.notation(notation), minusSign: minusSign)
}
}
extension ExtensibleFloatingPointFormatStyle : FormatStyle {
public typealias FormatInput = Value
public typealias FormatOutput = String
public func format(_ value: Value) -> String {
// Format base
var result = base.format(value)
// Replace minus sign if necessary.
//
// Warning: This assumes that the character "-" (ASCII hyphen-minus) doesn’t occur
// in the string except for the minus sign. This assumption is problematic.
// A better implementation could use the result of `base.attributed` to identify the
// sign reliably (it should have the attribute
// `AttributeScopes.FoundationAttributes.NumberFormatAttributes.SymbolAttribute.Symbol.sign`).
if let minusSign = minusSign {
result.replace("-", with: minusSign)
}
return result
}
}
extension FormatStyle where Self == ExtensibleFloatingPointFormatStyle<Double> {
public static var extNumber: ExtensibleFloatingPointFormatStyle<Double> {
ExtensibleFloatingPointFormatStyle()
}
}
extension FormatStyle where Self == ExtensibleFloatingPointFormatStyle<Float> {
public static var extNumber: ExtensibleFloatingPointFormatStyle<Float> {
ExtensibleFloatingPointFormatStyle()
}
}
// MARK: - Usage
let locale = Locale(identifier: "en_US")
let number = -123456.789
print(number.formatted(.number.locale(locale)))
print(number.formatted(.extNumber.locale(locale)))
// Test that the `.minusSign` API works.
print(number.formatted(.extNumber.locale(locale).minusSign("\u{2212}")))
print(number.formatted(.extNumber.locale(locale).minusSign("–––")))
// Test that the `.minusSign` API can be combined in arbitrary order with FloatingPointFormatStyle APIs.
print(number.formatted(.extNumber.locale(locale).grouping(.never).minusSign("\u{2212}").precision(.fractionLength(1))))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment