48 KiB
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
- Strings & Text
- Collections & Sequences
- Numbers & Geometry
- Date, Data & Files
- Encoding & Errors
- Crypto
- Core — the
LCEssentialsnamespace
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 2–20 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"
Words & search
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:) -> CGFloat — UIKit 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.
Bytes — Sequence 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 ** 3 → 8.0 |
√ n (prefix) |
square root → Double |
√ 9 → 3.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
Date — components, comparisons, formatting, arithmetic, init
Calendar-component accessors
Read (and, where noted, write) individual components using the user's current calendar.
| Property | Get | Set |
|---|---|---|
year |
✅ | ✅ |
month |
✅ | ✅ (clamped to valid range) |
day |
✅ | ✅ (clamped) |
hour / minute / second |
✅ | ✅ (clamped) |
nanosecond / millisecond |
✅ | ✅ (clamped) |
weekday / weekOfMonth / weekOfYear / quarter / era |
✅ | — |
calendar |
✅ (Calendar.current) |
— |
var d = Date()
d.year = 2030 // shifts the date to 2030, keeping everything else
d.minute = 0
Date().weekday // 1 = Sunday (Gregorian)
Relative checks
isInFuture, isInPast, isInToday, isInYesterday, isInTomorrow,
isInWeekend, isWorkday, isInCurrentWeek, isInCurrentMonth, isInCurrentYear.
someDate.isInToday // Bool
someDate.isInWeekend // Bool
func isInCurrent(_ component: Calendar.Component) -> Bool
Same granularity check, for an arbitrary component.
Date().isInCurrent(.year) // true
var iso8601String: String / var unixTimestamp: Double
yyyy-MM-dd'T'HH:mm:ss.SSS + Z (GMT); seconds since 1970.
Date().iso8601String // "2026-08-29T14:51:29.574Z"
Rounding
nearestFiveMinutes, nearestTenMinutes, nearestQuarterHour, nearestHalfHour, nearestHour — all return a new Date.
var d = Date(); d.minute = 44
d.nearestFiveMinutes // :45
d.nearestHour // rounds up because minute ≥ 30
var yesterday: Date / var tomorrow: Date
±1 day.
func adding(_:value:) -> Date / mutating func add(_:value:)
Add multiples of a calendar component.
Date().adding(.day, value: 7) // one week later
var d = Date(); d.add(.month, value: -1)
func changing(_:value:) -> Date?
Set one component to a specific value (validated; nil if out of range).
Date().changing(.hour, value: 9) // 9am today
func beginning(of component:) -> Date? / func end(of component:) -> Date?
Start / end instant of the enclosing .day / .month / .year / .hour / week, etc.
Date().beginning(of: .month) // 1st, 00:00:00
Date().end(of: .day) // 23:59:59
Differences
secondsSince(_:), minutesSince(_:), hoursSince(_:), daysSince(_:) — signed Double.
endDate.hoursSince(startDate) // e.g. 3.5
func isBetween(_ start: Date, _ end: Date, includeBounds: Bool = false) -> Bool
Range check.
func isWithin(_ value: UInt, _ component: Calendar.Component, of date: Date) -> Bool
Whether two dates are within N components of each other.
a.isWithin(3, .day, of: b) // true if ≤ 3 days apart
Formatting
| Method | Example output |
|---|---|
string(withFormat: String = "dd/MM/yyyy HH:mm") |
"29/08/2026 14:30" |
dateString(ofStyle: .medium) |
"Aug 29, 2026" |
dateTimeString(ofStyle: .short) |
"8/29/26, 2:30 PM" |
timeString(ofStyle: .short) |
"2:30 PM" |
dayName(ofStyle: .full / .threeLetters / .oneLetter) |
"Saturday" / "Sat" / "S" |
monthName(ofStyle: .full / .threeLetters / .oneLetter) |
"August" / "Aug" / "A" |
Date().string(withFormat: "yyyy-MM-dd") // "2026-08-29"
Date().dayName(ofStyle: .threeLetters) // "Sat"
Random dates
static func random(in: Range<Date>) -> Date, plus ClosedRange and
using generator: variants.
Date.random(in: startDate...endDate)
Initializers
init?(calendar:timeZone:era:year:month:day:hour:minute:second:nanosecond:)— every field defaults to "now".init?(iso8601String:)— parseyyyy-MM-dd'T'HH:mm:ss.SSSZ.init(unixTimestamp:)— seconds since 1970.init?(integerLiteral:)— parse a packedyyyyMMddinteger.
Date(year: 2010, month: 1, day: 12)
Date(iso8601String: "2026-01-12T16:48:00.959Z")
Date(integerLiteral: 2026_12_25)
Data — JSON, hashing, hex, XOR, decoding
var prettyJson: String?
Pretty-printed JSON if the data is a valid JSON value.
responseData.prettyJson
var toDictionay: [String: Any]?
Parse a JSON object to a dictionary (note the spelling — toDictionay).
var toHexString: String / func toHexadecimalString() -> String
Lower-case hex representation of the bytes.
Data([0x0f, 0xa0]).toHexString // "0fa0"
var bool: Bool
first != 0.
init?(hexString:)
Parse a hex string (spaces allowed) to bytes; nil on an invalid nibble.
Data(hexString: "0f a0") // 2 bytes
func SHA256() -> Data / func SHA512() -> Data
CommonCrypto digests (empty Data if CommonCrypto is unavailable).
Data("abc".utf8).SHA256().toHexString
func HMACSHA512(key: Data) -> Data — iOS 13+
CryptoKit HMAC-SHA512.
message.HMACSHA512(key: secret)
func XOR(with other: Data) -> Data
Byte-wise XOR (result length = shorter of the two).
a.XOR(with: pad)
static func MD5(string:) -> Data — iOS 13+
MD5 of a string (insecure — legacy interop only). Returns the hex digest as UTF-8 bytes.
Data.MD5(string: "Hello").toHexString
func object<T: Codable & Sendable>() -> T?
Decode JSON data to T, nil + logs on failure.
let user: User? = responseData.object()
URL
var params: [String: String]
Query items as a dictionary (missing values become "").
URL(string: "https://x.com?a=1&b=2")!.params // ["a": "1", "b": "2"]
FileManager — Documents-directory helpers
func createDirectory(_ directoryName: String) -> URL?
Create (if missing) a folder under Documents; returns its URL.
let dir = FileManager.default.createDirectory("cache")
func retrieveFile(_ directoryAndFile: String) -> URL
Build a file:// URL under Documents for the given relative path (no existence check).
func convertToURL(path: String) -> URL?
Directory URL under Documents, or nil if it can't be listed.
func saveFileToDirectory(_ sourceURL: URL, toPathURL: URL) -> Bool
moveItem(at:to:), returning success.
func saveImageToDirectory(_ imageWithPath: String, imagem: UIImage) -> Bool — UIKit only
Write a UIImage as PNG to an absolute path.
FileManager.default.saveImageToDirectory(path, imagem: photo)
func removeFile(_ directoryAndFile: String) -> Bool
removeItem(atPath:), returning success.
func retrieveAllFilesFromDirectory(directoryName: String) -> [String]?
File names in a Documents sub-folder (.DS_Store filtered out).
func directoryExistsAtPath(_ path: String) -> Bool
Exists and is a directory.
UserDefaults — Codable storage, common flags (@MainActor)
var isLoggedIn: Bool / var isFirstTimeOnApp: Bool
Ready-made boolean flags (keys "isLoggedIn" / "isFirstTimeOnApp"), auto-synchronize() on set.
UserDefaults.standard.isFirstTimeOnApp = false
func set<T: Codable>(object: T, forKey key: String, usingEncoder: = JSONEncoder())
Encode and store any Codable value.
func object<T: Codable>(_ type: T.Type, with key: String, usingDecoder: = JSONDecoder()) -> T?
Decode a stored Codable value.
UserDefaults.standard.set(object: user, forKey: "user")
let user = UserDefaults.standard.object(User.self, with: "user")
func removeSavedObject(forKey:) -> Bool
Remove a value only if it is a String; returns whether it removed anything.
func removeAllSaved()
Wipe the app's entire persistent domain.
func showEverything() -> [String: Any]
Full dictionaryRepresentation() — handy for debugging.
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"