Files
LCEssentials/Documentation/Extensions.md
2026-08-29 19:27:31 -03:00

40 KiB
Raw Blame History

LCEssentials — Extensions

Foundation, value-type, string, collection, numeric, date, and crypto helpers, plus the LCEssentials namespace itself. UIKit extensions live in UIKit.md; SwiftUI helpers in SwiftUI.md.

Every section is a collapsible block — click a heading to expand it.

Contents


API & Networking

API is an actor wrapping URLSession for typed JSON requests and multipart uploads. This is a summary — the full guide is in API.md (all parameters, error model, client certificates, custom body types, and the rationale vs. a hand-rolled URLSession).

API — typed async requests & uploads

API.shared

The shared actor instance. Every call is await; nothing runs on the main thread unless you hop there yourself.

struct User: Decodable, Sendable { let id: Int; let name: String }

let user: User = try await API.shared.request(
    url: "https://api.example.com/users/{id}",
    method: .get,
    pathParams: ["id": "42"]
)

request(url:method:body:pathParams:headers:debug:timeoutInterval:networkServiceType:persistConnection:)

Sends a request and decodes the JSON response into T: Decodable & Sendable. Non-2xx responses throw an NSError whose code is the HTTP status and whose localizedFailureReason is the response body. When T == String the raw body is returned without JSON decoding.

struct CreateUser: Encodable, Sendable { let name: String }

// JSON body
let created: User = try await API.shared.request(
    url: "https://api.example.com/users",
    method: .post,
    body: jsonBody(CreateUser(name: "Ana"))
)

// form-url-encoded body
let token: Token = try await API.shared.request(
    url: "https://api.example.com/oauth/token",
    method: .post,
    body: .form(["grant_type": "password", "username": "ana"])
)

upload(url:method:form:pathParams:headers:debug:timeoutInterval:networkServiceType:) and its onProgress: overload

Uploads a multipart/form-data body serialised to a temp file (removed afterwards) and streamed from disk, so large files never fully load into memory.

var form = MultipartForm()
form.field("caption", "Sunset")
form.file("photo", data: jpegData, filename: "p.jpg")
form.file("video", url: localVideoURL)          // streamed from disk

let result: UploadResult = try await API.shared.upload(
    url: "https://api.example.com/media",
    form: form,
    onProgress: { fraction in
        Task { @MainActor in progressView.progress = Float(fraction) }
    }
)

Body types — HTTPBody

jsonBody(_:) wraps any Encodable & Sendable; FormURLEncodedBody (a.k.a. .form([:])) percent-escapes every value and never drops one; RawBody lets you supply the bytes and Content-Type yourself. Conform your own type to HTTPBody and API accepts it with no change.

struct CSVBody: HTTPBody {
    let rows: [[String]]
    func encoded() throws -> (data: Data, contentType: String) {
        let text = rows.map { $0.joined(separator: ",") }.joined(separator: "\n")
        return (Data(text.utf8), "text/csv; charset=UTF-8")
    }
}

await API.shared.setupCertification(certData:password:)

Registers a client certificate (.p12) for mutual-TLS on subsequent requests.

let p12 = try Data(contentsOf: certURL)
await API.shared.setupCertification(certData: p12, password: "cert-pw")

Strings & Text

String — validation, parsing, formatting, masks, HTML, dates

URLs

var isValidUrl: Bool / var isValidHttpsUrl: Bool / var isValidHttpUrl: Bool

isValidUrl is true when URL(string:) succeeds; the other two additionally require the https / http scheme.

"https://google.com".isValidUrl        // true
"https://google.com".isValidHttpsUrl   // true
"http://google.com".isValidHttpsUrl    // false

var urlEncoded: String / var urlDecoded: String

Percent-encode (host-allowed set) / decode. urlDecoded returns the original string when it is not encoded.

"it's easy".urlEncoded       // "it's%20easy"
"it's%20easy".urlDecoded     // "it's easy"

mutating func urlEncode() -> String / mutating func urlDecode() -> String

In-place variants; also return the new value (@discardableResult).

var s = "a b"; s.urlEncode()   // s == "a%20b"

func stringByAddingPercentEncodingForRFC3986() -> String

Percent-encodes for use as a query key/value, escaping ;/?:@&=+$, and space.

"a&b=c".stringByAddingPercentEncodingForRFC3986()   // "a%26b%3Dc"

var url: String?

First URL detected inside the string (via NSDataDetector), or nil.

"visit www.site.com.br now".url   // "www.site.com.br"

var toURL: NSURL?

NSURL(string:) wrapper.

"https://x.com".toURL   // NSURL

Validation

var isEmail: Bool

Regex check for a syntactically valid email (TLD 220 chars).

"user@example.com".isEmail   // true
"nope".isEmail               // false

var isCPF: Bool

Validates a Brazilian CPF including both check digits. Strips formatting first (onlyNumbers), requires 11 digits.

"123.456.789-09".isCPF   // true when the check digits match

var isValidCNPJ: Bool

Validates a Brazilian CNPJ (14 digits, both check digits, rejects all-same-digit).

"11.222.333/0001-81".isValidCNPJ   // true when valid

var isAlphabetic: Bool

Letters only, no digits.

"abc".isAlphabetic     // true
"123abc".isAlphabetic  // false

var isAlphaNumeric: Bool

Contains at least one letter and one digit and nothing else — handy for password rules.

"123abc".isAlphaNumeric   // true
"abc".isAlphaNumeric      // false

var isHTML: Bool

True if the string contains an HTML tag.

"<b>hi</b>".isHTML   // true

func validateBolean(comparingBoolean: Bool = true) -> Bool

Loose truthy/falsy check against a large set of EN/PT words. With comparingBoolean: true returns whether the string means "true" (YES, ON, SIM, ATIVO, 1, T, …); with false, whether it means "false".

"SIM".validateBolean()                        // true
"nao".validateBolean(comparingBoolean: false) // true

Conversion

var bool: Bool?

"true"/"yes"/"1"true, "false"/"no"/"0"false, else nil (trimmed, case-insensitive).

" YES ".bool   // true
"maybe".bool   // nil

var int: Int? / var float: Float? / var double: Double?

Plain Int(self) / Float(self) / Double(self).

"101".int    // 101
"1.5".double // 1.5
"x".int      // nil

func float(locale: Locale = .current) -> Float? / func double(locale:) -> Double?

Locale-aware parsing via NumberFormatter (accepts grouping separators).

"1,5".double(locale: Locale(identifier: "pt_BR"))   // 1.5

var currencyStringToDouble: Double

Parses a pt_BR currency string to Double, 0.0 on failure.

"R$ 1.234,56".currencyStringToDouble   // 1234.56

var btcToSats: Int / var bitcoinToSatoshis: Int

Multiplies a BTC amount string by 100,000,000. bitcoinToSatoshis is an alias.

"0.0001".btcToSats   // 10000

var data: Data

UTF-8 bytes.

"hi".data   // 2 bytes

var nsString: NSString / var fullNSRange: NSRange

Bridge to NSString; NSRange spanning the whole string (UTF-16 aware).

"café".fullNSRange   // {0, 4}

func nsRange(from range: Range<String.Index>) -> NSRange?

Convert a Swift Range to an NSRange in the UTF-16 view.

var base64Encode: String? / var base64Decode: String?

Base64 encode the UTF-8 bytes / decode a Base64 string back to text.

"hi".base64Encode        // "aGk="
"aGk=".base64Decode      // "hi"

func date(withCurrFormatt:localeIdentifier:timeZone:) -> Date?

Parse the string to Date using the given input format (default "yyyy-MM-dd HH:mm:ss", locale pt-BR, current time zone). A " 0000" suffix is normalised to " +0000".

"2026-08-29 14:30:00".date()   // Date

func date(withCurrFormatt:newFormatt:localeIdentifier:timeZone:) -> Date?

Parse with one format and round-trip through another (normalises the value).

var currentTimeZone: String

Current time-zone offset string, e.g. "-0300".

Cleaning & filtering

var withoutSpacesAndNewLines: String

Removes every space and \n.

"  a \n b ".withoutSpacesAndNewLines   // "ab"

var onlyNumbers: String / var numbers: String

Digits only. onlyNumbers uses a \D regex; numbers uses decimalDigits.

"(11) 98765-4321".onlyNumbers   // "11987654321"

var letters: String / var lettersWithWhiteSpace: String

Keep only letters (optionally keeping spaces).

"a1 b2".letters                // "ab"
"a1 b2".lettersWithWhiteSpace  // "a b"

var alphanumeric: String / var alphanumericWithWhiteSpace: String

Keep only alphanumerics (optionally keeping spaces).

"a-b_c 1".alphanumeric   // "abc1"

var removeSpecialChars: String

Keeps [A-Za-z0-9 -] only.

"a@b#c".removeSpecialChars   // "abc"

var removeHTMLTags: String / var removeEmoji: String

Strip HTML tags / strip emoji (CharacterSet.symbols).

"<p>hi</p>".removeHTMLTags   // "hi"
"hi 😀".removeEmoji           // "hi "

Slicing & padding

var first: String / var last: String

First / last character as a String ("" when empty).

var uppercaseFirst: String

Capitalises the first character only.

"hello".uppercaseFirst   // "Hello"

var firstCharacterAsString: String? / var lastCharacterAsString: String?

Optional variants — nil when empty.

func paddingStart(_ length: Int, with: String = " ") -> String / func paddingEnd(...)

Pad to length with a repeating pad string at the start / end. No-op if already long enough.

"hue".paddingStart(10)             // "       hue"
"hue".paddingEnd(10, with: "br")   // "huebrbrbrb"

func truncated(toLength: Int, trailing: String? = "...") -> String

Non-mutating truncation.

"This is long".truncated(toLength: 7)   // "This is..."

mutating func truncate(toLength: Int, trailing: String? = "...") -> String

In-place truncation (@discardableResult).

mutating func trim() -> String

Trim leading/trailing whitespace and newlines, in place (@discardableResult).

var s = "  hi \n"; s.trim()   // s == "hi"

mutating func reverse() -> String

Reverse in place (@discardableResult).

mutating func insertAtIndexEnd(string:ind:) / insertAtIndexStart(string:ind:)

Insert string at an offset measured from endIndex (negative ind moves left).

var s = "abcd"; s.insertAtIndexEnd(string: "-", ind: -1)   // "abc-d"

Replacing

func replace(from:to:) / func findAndReplace(from:to:)

Simple substring replacement (findAndReplace is generic over StringProtocol).

"a.b.c".replace(from: ".", to: "-")   // "a-b-c"

func replacing(range: CountableClosedRange<Int>, with: String) -> String

Replace by integer character range.

"abcdef".replacing(range: 1...3, with: "X")   // "aXef"

func replacingLastOccurrenceOfString(_:with:caseInsensitive: Bool = true) -> String

Replace only the last match.

"a-b-c".replacingLastOccurrenceOfString("-", with: "+")   // "a-b+c"

func replaceAll(of pattern: String, with: String, options: = []) -> String

Regex replace-all; returns the original on a bad pattern.

"a1b2c3".replaceAll(of: "[0-9]", with: "#")   // "a#b#c#"

@discardableResult func replaceURL(_ withDict: [String: Any]) -> String

Substitute {key} placeholders — used by API.request(pathParams:).

"/users/{id}/posts/{p}".replaceURL(["id": 7, "p": "x"])   // "/users/7/posts/x"

func words() -> [String] / func wordCount() -> Int

Split on whitespace + punctuation, dropping empties.

"Swift is amazing".words()      // ["Swift", "is", "amazing"]
"Swift is amazing".wordCount()  // 3

func contains(_:caseSensitive: Bool = true) -> Bool

Substring check with optional case-insensitivity.

"Hello".contains("ell")                       // true
"Hello".contains("HELLO", caseSensitive: false) // true

Formatting helpers

func applyMask(toText: String, mask: String) -> String

Apply a #-placeholder mask; literal characters in the mask are inserted.

"11987654321".applyMask(toText: "11987654321", mask: "(##) #####-####")
// "(11) 98765-4321"

func exponentize(str: String) -> String

Turn ^-prefixed digits into Unicode superscripts.

"x^2 + y^3".exponentize(str: "x^2 + y^3")   // "x² + y³"

func stringFromTimeInterval(_ interval: TimeInterval) -> NSString

Format a TimeInterval as HH:MM:SS.mmm.

"".stringFromTimeInterval(3661.5)   // "01:01:01.500"

func toSlug() -> String

Lowercase, de-accent, spaces → -, strip other punctuation.

"Olá Mundo!".toSlug()   // "ola-mundo"

func localized(comment: String = "") -> String

NSLocalizedString(self, comment:).

"welcome_title".localized()

Generators & misc

static func loremIpsum(ofLength length: Int = 445) -> String

Lorem-ipsum text truncated to length (max 445).

String.loremIpsum(ofLength: 20)   // "Lorem ipsum dolor si"

func randomString(length: Int) -> String

Random [A-Za-z0-9] string. (Instance method — the receiver is ignored.)

"".randomString(length: 8)   // e.g. "a9Fk2Lp0"

var JSONStringToDictionary: [String: Any]?

Parse a JSON object string to a dictionary (nil + logs on failure).

#"{"a":1}"#.JSONStringToDictionary   // ["a": 1]

var convertToHTML: NSAttributedString?

Render an HTML string to NSAttributedString (UIKit path uses the CSS converter below).

func convertHtmlToAttributedStringWithCSS(font:csscolor:lineheight:csstextalign:customCSS:) -> NSAttributedString?UIKit only

HTML → NSAttributedString with an injected <style> block. Returns the plain HTML rendering when font is nil.

"<b>Hi</b>".convertHtmlToAttributedStringWithCSS(
    font: .systemFont(ofSize: 16), csscolor: "#333",
    lineheight: 0, csstextalign: "left"
)

func height(withConstrainedWidth:font:) -> CGFloat / func width(withConstraintedHeight:font:) -> CGFloatUIKit only

Measured bounding height/width for the string at a fixed width/height and font.

"Some label text".height(withConstrainedWidth: 200, font: .systemFont(ofSize: 14))
Character — classification, conversion, repetition

var isEmoji: Bool

True when the scalar falls in a known emoji range.

Character("😀").isEmoji   // true
Character("a").isEmoji    // false

var int: Int? / var string: String

Digit value (or nil) / one-character String.

Character("7").int   // 7
Character("A").int   // nil

var lowercased: Character / var uppercased: Character

Case-flipped character.

Character("a").uppercased   // "A"

func unicodeScalarCodePoint() -> UInt32

First Unicode scalar value.

Character("A").unicodeScalarCodePoint()   // 65

static func randomAlphanumeric() -> Character

Random [A-Za-z0-9] character.

Character.randomAlphanumeric()   // e.g. "k"

static func * (Character, Int) -> String / static func * (Int, Character) -> String

Repeat a character into a string.

Character("-") * 5   // "-----"
5 * Character("-")   // "-----"
NSString

var string: String?

String(describing:) of the NSString.

func randomAlphaNumericString(_ length: Int = 8) -> String

Random [A-Za-z0-9] string of the given length.

("" as NSString).randomAlphaNumericString(12)
NSAttributedString / NSMutableAttributedString

NSAttributedString(html: String)failable init

Build an attributed string from an HTML fragment.

label.attributedText = NSAttributedString(html: "<b>Hello</b> world")

NSMutableAttributedString builders — UIKit only, all @discardableResult and chainable

Method Effect
customize(_:withFont:color:lineSpace:alignment:changeCurrentText:) append (or restyle) a run with font/color/spacing/alignment
underline(_:withFont:color:changeCurrentText:) underlined run
strikethrough(_:changeCurrentText:) strikethrough run
linkTouch(_:url:withFont:color:changeCurrentText:) tappable link run
supperscript(_:withFont:color:offset:changeCurrentText:) baseline-offset (superscript) run
appendImageToText(_:) append an inline UIImage attachment
normal(_:) append an unstyled run

changeCurrentText: true restyles the first occurrence of the text already in the string instead of appending.

let s = NSMutableAttributedString()
    .customize("Total: ", withFont: .systemFont(ofSize: 14))
    .customize("R$ 10", withFont: .boldSystemFont(ofSize: 14), color: .label)
    .supperscript("00", withFont: .systemFont(ofSize: 9), offset: 6)
label.attributedText = s

func canSetAsLink(textToFind: String, linkURL: String) -> Bool

Adds a .link attribute to the first match; returns whether it was found.

func attributtedString() -> NSAttributedString

Immutable copy of the whole string.

func height(withConstrainedWidth:) -> CGFloat / func width(withConstrainedHeight:) -> CGFloat

Bounding size for the attributed content.

BytesSequence where Element == UInt8

var data: Data

Wrap the byte sequence in Data.

var base64Decoded: Data?

Interpret the bytes as Base64 text and decode.

var string: String?

Decode the bytes as UTF-8.

let bytes: [UInt8] = [0x68, 0x69]
bytes.data       // 2 bytes
bytes.string     // "hi"

Collections & Sequences

Array — dedup, mutation helpers (Element: Equatable)

var unique: [Element] / func withoutDuplicates() -> [Element]

New array with duplicates dropped, first occurrence kept (order preserved).

[1, 2, 2, 3].unique   // [1, 2, 3]

mutating func removeDuplicates() -> [Element]

In-place dedup (@discardableResult).

func withoutDuplicates<E: Equatable>(keyPath:) -> [Element] / <E: Hashable>(keyPath:)

Dedup by a key path. The Hashable overload is O(n).

users.withoutDuplicates(keyPath: \.id)

var removeNilElements: [Element]

compactMap { $0 } — only meaningful when Element is itself optional.

mutating func removeAll(_ item: Element) -> [Element] / removeAll(_ items: [Element])

Remove every occurrence of one value / of any value in a list (@discardableResult).

var a = [1, 2, 2, 3]; a.removeAll(2)          // [1, 3]
var b = [1, 2, 3, 4]; b.removeAll([2, 4])     // [1, 3]

mutating func prepend(_ newElement: Element)

Insert at index 0.

mutating func safeSwap(from:to:)

Swap two indices; silently no-ops if either is out of bounds or equal.

var a = [1, 2, 3]; a.safeSwap(from: 0, to: 2)   // [3, 2, 1]
a.safeSwap(from: 0, to: 9)                       // unchanged
Collection — safe indexing, chunking, indices, averages

var fullRange: Range<Index>

startIndex..<endIndex.

subscript(safe index:) -> Element? / subscript(exist index:) -> Element?

Bounds-checked access, nil instead of a crash.

let a = [1, 2, 3]
a[safe: 1]    // 2
a[safe: 9]    // nil

func group(by size: Int) -> [[Element]]?

Split into chunks of size (last chunk may be shorter). nil when empty or size <= 0.

[0, 2, 4, 7, 6].group(by: 2)   // [[0, 2], [4, 7], [6]]

func forEach(slice: Int, body: ([Element]) -> Void)

Iterate in chunks without building the intermediate array.

[0, 2, 4, 7].forEach(slice: 2) { print($0) }   // [0, 2] then [4, 7]

func indices(where condition:) -> [Index]?

All indices matching a predicate, nil if none.

[1, 7, 1, 2, 1].indices(where: { $0 == 1 })   // [0, 2, 4]

func indices(of item: Element) -> [Index](Element: Equatable)

All indices equal to item.

func adjacentPairs() -> AnySequence<(Element, Element)>

Every unordered pair (i, j) with i before j.

Array([1, 2, 3].adjacentPairs())   // [(1, 2), (1, 3), (2, 3)]

func forEachInParallel(_:)

DispatchQueue.concurrentPerform over the elements. No ordering guarantee.

func average() -> Double (Element: BinaryInteger) / func average() -> Element (Element: FloatingPoint)

Mean, 0 for an empty collection.

[1, 2, 3, 4].average()        // 2.5
[1.2, 2.3, 4.5].average()     // 2.666…
BidirectionalCollection

subscript(offset distance: Int) -> Element

Positive offset from the start, negative from the end.

let a = [1, 2, 3, 4, 5]
a[offset: 1]    // 2
a[offset: -2]   // 4

func last<T: Equatable>(where keyPath:, equals value:) -> Element?

Last element whose key-path value equals value.

events.last(where: \.type, equals: .login)
Sequence — predicates, key-path sorting, sums, dedup

func all(matching:) / func none(matching:) / func any(matching:)

Whether the predicate holds for all / no / at least one element.

[2, 4, 6].all(matching: { $0 % 2 == 0 })   // true
[1, 3].any(matching: { $0 % 2 == 0 })      // false

func reject(where:) -> [Element]

Inverse of filter.

[2, 3, 4, 7].reject(where: { $0 % 2 == 0 })   // [3, 7]

func count(where:) -> Int

Number of elements matching a predicate.

func forEachReversed(_:) / func forEach(where:body:)

Iterate right-to-left / iterate only matching elements.

func accumulate<U>(initial:next:) -> [U]

Like reduce but returns every interim result.

[1, 2, 3].accumulate(initial: 0, next: +)   // [1, 3, 6]

func filtered<T>(_ isIncluded:, map transform:) -> [T]

filter + map in one lazy pass.

[1, 2, 3, 4].filtered({ $0 % 2 == 0 }, map: { "\($0)" })   // ["2", "4"]

func single(where:) -> Element?

The one matching element, or nil if zero or more than one match.

[1, 4, 7].single(where: { $0 % 2 == 0 })      // 4
[2, 4].single(where: { $0 % 2 == 0 })         // nil

func divided(by condition:) -> (matching:, nonMatching:)

Partition into two arrays.

let (even, odd) = [0, 1, 2, 3].divided { $0 % 2 == 0 }   // ([0, 2], [1, 3])

func withoutDuplicates<T: Hashable>(transform:) -> [Element]

Dedup by a derived hashable value.

[(1, "a"), (2, "b"), (1, "c")].withoutDuplicates { $0.0 }   // [(1, "a"), (2, "b")]

func sorted(by keyPath:) / sorted(by keyPath:with:) / sorted(by:and:) / sorted(by:and:and:)

Sort by one, two, or three key paths (later paths break ties). The with: variant takes an explicit comparator.

people.sorted(by: \.lastName, and: \.firstName)
scores.sorted(by: \.value, with: >)

func sum() -> Element (Element: AdditiveArithmetic) / func sum<T: AdditiveArithmetic>(for keyPath:) -> T

Total of the elements, or of a numeric property.

[1, 2, 3].sum()                         // 6
["ab", "cde"].sum(for: \.count)         // 5

func first<T: Equatable>(where keyPath:, equals value:) -> Element?

First element matching on a key path.

func contains(_ elements: [Element]) -> Bool (Element: Equatable or Hashable)

Whether every element of elements is present (the Hashable overload is O(m+n)).

func containsDuplicates() -> Bool / func duplicates() -> [Element] (Element: Hashable)

Whether any value repeats / the set of repeated values.

[1, 2, 2, 3, 3].duplicates().sorted()   // [2, 3]
RangeReplaceableCollection — rotate, take/skip, offset subscripts

init(expression:count:)

Build a collection by evaluating an autoclosure count times.

Array(expression: Int.random(in: 0..<10), count: 3)   // e.g. [4, 9, 1]

func rotated(by places: Int) -> Self / mutating func rotate(by:) -> Self

Rotate elements; positive moves the tail to the front.

[1, 2, 3, 4].rotated(by: 1)    // [4, 1, 2, 3]
[1, 2, 3, 4].rotated(by: -1)   // [2, 3, 4, 1]

mutating func removeFirst(where:) -> Element?

Remove and return the first match (@discardableResult).

mutating func removeRandomElement() -> Element?

Remove and return a random element.

mutating func keep(while:) -> Self / func take(while:) -> Self / func skip(while:) -> Self

keep/take return the leading run that matches; skip returns everything after it.

[0, 2, 4, 7, 6].take(while: { $0 % 2 == 0 })   // [0, 2, 4]
[0, 2, 4, 7, 6].skip(while: { $0 % 2 == 0 })   // [7, 6]

mutating func removeDuplicates<E>(keyPath:)

In-place dedup by an Equatable or Hashable key path.

subscript(offset: Int) -> Element / subscript<R: RangeExpression>(range:) -> SubSequence

Get/set by integer offset or integer range.

var a = [10, 20, 30]; a[1] = 99      // [10, 99, 30]
a[0..<2]                              // [10, 99]

mutating func appendIfNonNil(_:) / appendIfNonNil(contentsOf:)

Append only when the optional value / sequence is non-nil.

var a = [1]; a.appendIfNonNil(Optional<Int>.none)   // [1]
a.appendIfNonNil(2)                                  // [1, 2]
Dictionary — key paths, JSON, key/value maps, merge operators

subscript(path path: [Key]) -> Any?

Deep get/set through nested dictionaries.

var d: [String: Any] = ["a": ["b": ["c": 1]]]
d[path: ["a", "b", "c"]]              // 1
d[path: ["a", "b", "c"]] = 2

var queryString: String

key=value&key=value (no percent-encoding — encode yourself if needed).

["a": 1, "b": 2].queryString   // "a=1&b=2" (order not guaranteed)

var convertToJSON: String

Pretty-printed JSON, or an error description string on failure.

init(grouping sequence: by keyPath:)

Group a sequence into [Key: [Element]] by a key path.

Dictionary(grouping: people, by: \.city)

func toObjetct<T: Codable & Sendable>() throws -> T

Encode to JSON then decode into T. Throws DecodingError on a shape mismatch.

let user: User = try ["id": 1, "name": "Ana"].toObjetct()

func has(key:) -> Bool

Key presence check.

mutating func removeAll<S: Sequence>(keys:) / static func - (lhs:keys:) / static func -= (lhs:keys:)

Remove a set of keys — mutating, or as a new dictionary via - / -=.

var d = ["a": 1, "b": 2, "c": 3]
d -= ["a", "b"]                       // ["c": 3]

mutating func removeValueForRandomKey() -> Value?

Remove and return one random entry's value.

func jsonData(prettify: Bool = false) -> Data? / func jsonString(prettify: Bool = false) -> String?

Serialise to Data / String, nil if the dictionary isn't a valid JSON object.

func mapKeysAndValues<K, V>(_:) -> [K: V] / func compactMapKeysAndValues<K, V>(_:) -> [K: V]

Transform both keys and values in one pass; the compact variant drops nil results.

["a": 1].mapKeysAndValues { ($0.key.uppercased(), $0.value * 10) }   // ["A": 10]

func pick(keys: [Key]) -> [Key: Value]

Sub-dictionary limited to the given keys.

static func + (lhs:rhs:) / static func += (lhs:rhs:)

Merge two dictionaries (right wins on key clash).

func keys(forValue value:) -> [Key] (Value: Equatable)

All keys mapping to a value.

mutating func lowercaseAllKeys() (Key: StringProtocol)

Lowercase every key in place.

var uniqueValues: [Key: Value] (Value: Hashable)

Keep only the first entry seen for each distinct value.

Optional — safe unwrap, conditional assignment

func unwrapped(or defaultValue: Wrapped) -> Wrapped

self ?? defaultValue, read nicely.

let name: String? = nil
name.unwrapped(or: "Guest")   // "Guest"

func unwrapped(or error: Error) throws -> Wrapped

Unwrap or throw a chosen error.

let id = try userId.unwrapped(or: AppError.missingID)

func run(_ block: (Wrapped) -> Void)

Run a block only when non-nil (like if let, expression-style).

token.run { print("have token \($0)") }

static func ??= (lhs: inout Optional, rhs: Optional)

Assign only if the right side is non-nil.

var params: String? = "a"; params ??= nil   // still "a"

static func ?= (lhs: inout Optional, rhs: @autoclosure)

Assign only if the left side is currently nil.

var text: String? = nil
text ?= "first"     // "first"
text ?= "second"    // still "first"

var isNilOrEmpty: Bool / var nonEmpty: Wrapped? (Wrapped: Collection)

Nil-or-empty check; nonEmpty returns the collection only when it has content.

let list: [Int]? = []
list.isNilOrEmpty   // true
list.nonEmpty       // nil

static func == / != (Optional, Wrapped.RawValue?) (Wrapped: RawRepresentable)

Compare an optional enum directly against an optional raw value.

let status: Status? = .active
status == "active"   // true
Comparable

func isBetween(_ range: ClosedRange<Self>) -> Bool

Range membership.

7.isBetween(6...12)   // true

func clamped(to range: ClosedRange<Self>) -> Self

Constrain a value to a range.

1.clamped(to: 3...8)      // 3
0.32.clamped(to: 0.1...0.29)   // 0.29
Bool

var int: Int / var string: String / var data: Data

1/0, "true"/"false", or a single-byte Data.

true.int      // 1
false.string  // "false"

Numbers & Geometry

Int — conversions, digits, primes, roman numerals, operators

var double: Double / var float: Float / var cgFloat: CGFloat / var uInt: UInt / var uInt32: UInt32 / var uInt64: UInt64

Straight numeric conversions (uInt32/uInt64 truncate).

5.double    // 5.0
(-1).uInt32 // 4294967295

var countableRange: CountableRange<Int>

0..<self.

3.countableRange   // 0..<3

var degreesToRadians: Double / var radiansToDegrees: Double

Angle conversion.

180.degreesToRadians   // 3.14159…

var digits: [Int] / var digitsCount: Int

Decimal digits of abs(self), and how many.

1234.digits       // [1, 2, 3, 4]
1234.digitsCount  // 4

var kFormatted: String

Compact "k"/"kk" formatting for values ≥ 1000.

5300.kFormatted     // "5k"
2_500_000.kFormatted // "25kk"

var timestampToDate: Date

Date(timeIntervalSince1970:).

1_700_000_000.timestampToDate   // 2023-11-14 …

var satsToBTC: String / var convertToBTC: String / var toBTC: String

Satoshis → BTC string, 8 decimal places. All three are the same.

150_000_000.satsToBTC   // "1.50000000"

func isPrime() -> Bool

Primality test (trial division up to √n).

7.isPrime()   // true
9.isPrime()   // false

func romanNumeral() -> String?

Roman numerals for positive integers, nil for 0 or negative.

2024.romanNumeral()   // "MMXXIV"

func roundToNearest(_ number: Int) -> Int

Round to the closest multiple of number.

47.roundToNearest(10)   // 50

Operators

Operator Meaning Example
a ** b exponentiation → Double 2 ** 38.0
√ n (prefix) square root → Double √ 93.0
a ± b (infix) (a+b, a-b) 5 ± 3(8, 2)
± n (prefix) (n, -n) ± 2(2, -2)
Float / Double

var int: Int / var double: Double (Float) / var float: Float (Double) / var cgFloat: CGFloat

Numeric conversions.

var satsToBTC / convertToBTC / toBTC

Satoshis → BTC (Double here, unlike Int which returns a String).

150_000_000.0.satsToBTC   // 1.5

func rounded(toPlaces places: Int) -> Float(Float only)

Round to N decimal places.

Float(3.14159).rounded(toPlaces: 2)   // 3.14

Operator a ** b

Exponentiation, staying in the same type (Float ** Float → Float, Double ** Double → Double).

4.4 ** 0.5   // 2.0976…
Decimal

mutating func round(_ scale: Int, _ roundingMode:) / func rounded(_ scale:, _ roundingMode:) -> Decimal

Decimal rounding via NSDecimalRound — mutating and non-mutating.

Decimal(2.567).rounded(2, .plain)   // 2.57
BinaryInteger

var bytes: [UInt8]

Big-endian raw byte representation.

Int16(-128).bytes   // [255, 128]

init?(bytes: [UInt8])

Reconstruct an integer from bytes (traps if the byte count exceeds the type size; nil if the value doesn't fit exactly).

Int16(bytes: [0xFF, 0xFD])   // -3
BinaryFloatingPoint

func rounded(numberOfDecimalPlaces: Int, rule: FloatingPointRoundingRule) -> Self

Round to N places with an explicit rule (negative places treated as 0).

3.1415927.rounded(numberOfDecimalPlaces: 3, rule: .up)   // 3.142
SignedNumeric

var string: String

String(describing:).

var asLocaleCurrency: String? / func asCurrency(locale: Locale = pt_BR) -> String?

Currency formatting in the current locale / a specified locale.

1234.5.asCurrency()                                  // "R$ 1.234,50"
1234.5.asCurrency(locale: Locale(identifier: "en_US")) // "$1,234.50"

func spelledOutString(locale: Locale = .current) -> String?

Number spelled out in words.

92.spelledOutString(locale: Locale(identifier: "en"))   // "ninety-two"
CGFloat

var abs / ceil / floor / var int / float / double

Math and numeric conversions.

CGFloat(-3.2).abs    // 3.2
CGFloat(3.2).ceil    // 4.0

var isPositive: Bool / var isNegative: Bool

Sign checks.

var degreesToRadians: CGFloat / var radiansToDegrees: CGFloat

Angle conversion.

CGRect

var center: CGPoint

Rect centre.

init(center: CGPoint, size: CGSize)

Build a rect from its centre and size.

CGRect(center: CGPoint(x: 50, y: 50), size: CGSize(width: 20, height: 10))
// origin (40, 45), size 20×10

func resizing(to size: CGSize, anchor: CGPoint = (0.5, 0.5)) -> CGRect

Resize while keeping the given normalised anchor point fixed.

rect.resizing(to: CGSize(width: 100, height: 100), anchor: CGPoint(x: 0, y: 1))
// grows from the bottom-left corner
CGSize

var aspectRatio: CGFloat / var maxDimension: CGFloat / var minDimension: CGFloat

width / height (0 when height is 0), and the larger / smaller side.

CGSize(width: 16, height: 9).aspectRatio   // 1.777…

func aspectFit(to boundingSize:) -> CGSize / func aspectFill(to boundingSize:) -> CGSize

Scale to fit inside / fill a bounding size, preserving ratio.

CGSize(width: 120, height: 80).aspectFit(to: CGSize(width: 100, height: 50))
// 75 × 50

Operators

+, -, * and their +=/-=/*= forms, between two CGSizes, a CGSize and a (width, height) tuple, or a CGSize and a scalar.

CGSize(width: 5, height: 10) + CGSize(width: 3, height: 4)   // 8 × 14
CGSize(width: 5, height: 10) * 3                              // 15 × 30
CGPoint / CGRect / CGSize — Hashable

When SwiftUI is available, CGPoint, CGRect, and CGSize are made Hashable (retroactive conformance) so they can be used as dictionary keys or in Sets and as SwiftUI identifiers.

var seen: Set<CGPoint> = []
seen.insert(CGPoint(x: 1, y: 2))

Date, Data & Files

Encoding & Errors

Crypto

RIPEMD_160 — RIPEMD-160 digest

static func hash(_ message: Data) -> Data

Returns the 20-byte RIPEMD-160 digest of message. Pure Swift, no system dependency. Mainly useful for Bitcoin-style address hashing (RIPEMD160(SHA256(x))).

let digest = RIPEMD_160.hash(Data("abc".utf8))
digest.count                                   // 20
digest.map { String(format: "%02x", $0) }.joined()
// "8eb208f7e05d987a9b044a8e98c6b087f15a0bfc"
LCECryptoKitManager — OTP / peppered-login bridge (needs the LCECryptoKit binary)

A thin facade over the optional LCECryptoKit binary product (enabled by the LCE_ENABLE_CRYPTO_BINARY build flag). When the binary is not linked every method is a no-op returning nil / "" / false, so calling code still compiles and runs.

init() / init(privateKey:)

Create a manager. The privateKey (a.k.a. "hash key") is only needed by the *WithKey methods.

let crypto = LCECryptoKitManager()
let keyed  = LCECryptoKitManager(privateKey: serverHashKey)

static func generateKey() -> String

Generates a random AES key string.

let key = LCECryptoKitManager.generateKey()

func encodeTP(email:password:) -> String? / func decodeOTP(_:) -> String?

Encode an email+password pair into an OTP seed hash, and decode it back.

let hash = crypto.encodeTP(email: "ana@x.com", password: "s3cr3t")
let back = crypto.decodeOTP(hash ?? "")

func encodeOTPWithKey(email:password:) -> String? / func decodeOTPWithKey(_:) -> Bool

Same as above but bound to the instance's privateKey; decodeOTPWithKey returns whether the hash validates against that key rather than the decoded value.

let keyed = LCECryptoKitManager(privateKey: serverHashKey)
let hash  = keyed.encodeOTPWithKey(email: "ana@x.com", password: "s3cr3t")
let ok    = keyed.decodeOTPWithKey(hash ?? "")     // Bool

static func generateSalt() -> String

Random salt for the salted/iterated/peppered login flow.

let salt = LCECryptoKitManager.generateSalt()

static func computeClientHash(email:password:salt:) -> String

Client-side hash of the credentials with the given salt — sent to the server instead of the raw password.

let clientHash = LCECryptoKitManager.computeClientHash(
    email: "ana@x.com", password: "s3cr3t", salt: salt
)

static func computeLoginBearerToken(userId:clientHash:) -> String?

Derives the login bearer token from a user id and the client hash.

let token = LCECryptoKitManager.computeLoginBearerToken(userId: "42", clientHash: clientHash)

static func otpEncode(_:) -> String? / static func otpDecode(_:) -> String?

One-time-pad encode/decode of an arbitrary string.

let enc = LCECryptoKitManager.otpEncode("secret-value")
let dec = LCECryptoKitManager.otpDecode(enc ?? "")     // "secret-value"

Core — the LCEssentials namespace