2026-08-29 19:22:12 -03:00
|
|
|
|
# LCEssentials — Extensions
|
|
|
|
|
|
|
|
|
|
|
|
Foundation, value-type, string, collection, numeric, date, and crypto helpers, plus
|
|
|
|
|
|
the `LCEssentials` namespace itself. UIKit extensions live in
|
|
|
|
|
|
[UIKit.md](UIKit.md); SwiftUI helpers in [SwiftUI.md](SwiftUI.md).
|
|
|
|
|
|
|
|
|
|
|
|
Every section is a collapsible block — click a heading to expand it.
|
|
|
|
|
|
|
|
|
|
|
|
## Contents
|
|
|
|
|
|
|
|
|
|
|
|
- [API & Networking](#api--networking)
|
|
|
|
|
|
- [Strings & Text](#strings--text)
|
|
|
|
|
|
- [Collections & Sequences](#collections--sequences)
|
|
|
|
|
|
- [Numbers & Geometry](#numbers--geometry)
|
|
|
|
|
|
- [Date, Data & Files](#date-data--files)
|
|
|
|
|
|
- [Encoding & Errors](#encoding--errors)
|
|
|
|
|
|
- [Crypto](#crypto)
|
|
|
|
|
|
- [Core — the `LCEssentials` namespace](#core--the-lcessentials-namespace)
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## API & Networking
|
|
|
|
|
|
|
2026-08-29 19:23:23 -03:00
|
|
|
|
`API` is an `actor` wrapping `URLSession` for typed JSON requests and multipart
|
|
|
|
|
|
uploads. This is a summary — the **[full guide is in API.md](API.md)** (all
|
|
|
|
|
|
parameters, error model, client certificates, custom body types, and the
|
|
|
|
|
|
rationale vs. a hand-rolled `URLSession`).
|
|
|
|
|
|
|
|
|
|
|
|
<details>
|
|
|
|
|
|
<summary><b>API</b> — typed async requests & uploads</summary>
|
|
|
|
|
|
|
|
|
|
|
|
### `API.shared`
|
|
|
|
|
|
|
|
|
|
|
|
The shared `actor` instance. Every call is `await`; nothing runs on the main
|
|
|
|
|
|
thread unless you hop there yourself.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
let p12 = try Data(contentsOf: certURL)
|
|
|
|
|
|
await API.shared.setupCertification(certData: p12, password: "cert-pw")
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
</details>
|
2026-08-29 19:22:12 -03:00
|
|
|
|
|
|
|
|
|
|
## Strings & Text
|
|
|
|
|
|
|
2026-08-29 19:25:15 -03:00
|
|
|
|
<details>
|
|
|
|
|
|
<summary><b>String</b> — validation, parsing, formatting, masks, HTML, dates</summary>
|
|
|
|
|
|
|
|
|
|
|
|
### 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.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
"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.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
"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`).
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
var s = "a b"; s.urlEncode() // s == "a%20b"
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### `func stringByAddingPercentEncodingForRFC3986() -> String`
|
|
|
|
|
|
Percent-encodes for use as a query key/value, escaping `;/?:@&=+$,` and space.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
"a&b=c".stringByAddingPercentEncodingForRFC3986() // "a%26b%3Dc"
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### `var url: String?`
|
|
|
|
|
|
First URL detected **inside** the string (via `NSDataDetector`), or `nil`.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
"visit www.site.com.br now".url // "www.site.com.br"
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### `var toURL: NSURL?`
|
|
|
|
|
|
`NSURL(string:)` wrapper.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
"https://x.com".toURL // NSURL
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### Validation
|
|
|
|
|
|
|
|
|
|
|
|
#### `var isEmail: Bool`
|
|
|
|
|
|
Regex check for a syntactically valid email (TLD 2–20 chars).
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
"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.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
"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).
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
"11.222.333/0001-81".isValidCNPJ // true when valid
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### `var isAlphabetic: Bool`
|
|
|
|
|
|
Letters only, no digits.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
"abc".isAlphabetic // true
|
|
|
|
|
|
"123abc".isAlphabetic // false
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### `var isAlphaNumeric: Bool`
|
|
|
|
|
|
Contains at least one letter **and** one digit and nothing else — handy for
|
|
|
|
|
|
password rules.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
"123abc".isAlphaNumeric // true
|
|
|
|
|
|
"abc".isAlphaNumeric // false
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### `var isHTML: Bool`
|
|
|
|
|
|
True if the string contains an HTML tag.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
"<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".
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
"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).
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
" YES ".bool // true
|
|
|
|
|
|
"maybe".bool // nil
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### `var int: Int?` / `var float: Float?` / `var double: Double?`
|
|
|
|
|
|
Plain `Int(self)` / `Float(self)` / `Double(self)`.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
"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).
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
"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.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
"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.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
"0.0001".btcToSats // 10000
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### `var data: Data`
|
|
|
|
|
|
UTF-8 bytes.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
"hi".data // 2 bytes
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### `var nsString: NSString` / `var fullNSRange: NSRange`
|
|
|
|
|
|
Bridge to `NSString`; `NSRange` spanning the whole string (UTF-16 aware).
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
"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.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
"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"`.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
"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`.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
" a \n b ".withoutSpacesAndNewLines // "ab"
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### `var onlyNumbers: String` / `var numbers: String`
|
|
|
|
|
|
Digits only. `onlyNumbers` uses a `\D` regex; `numbers` uses `decimalDigits`.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
"(11) 98765-4321".onlyNumbers // "11987654321"
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### `var letters: String` / `var lettersWithWhiteSpace: String`
|
|
|
|
|
|
Keep only letters (optionally keeping spaces).
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
"a1 b2".letters // "ab"
|
|
|
|
|
|
"a1 b2".lettersWithWhiteSpace // "a b"
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### `var alphanumeric: String` / `var alphanumericWithWhiteSpace: String`
|
|
|
|
|
|
Keep only alphanumerics (optionally keeping spaces).
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
"a-b_c 1".alphanumeric // "abc1"
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### `var removeSpecialChars: String`
|
|
|
|
|
|
Keeps `[A-Za-z0-9 -]` only.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
"a@b#c".removeSpecialChars // "abc"
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### `var removeHTMLTags: String` / `var removeEmoji: String`
|
|
|
|
|
|
Strip HTML tags / strip emoji (`CharacterSet.symbols`).
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
"<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.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
"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.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
"hue".paddingStart(10) // " hue"
|
|
|
|
|
|
"hue".paddingEnd(10, with: "br") // "huebrbrbrb"
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### `func truncated(toLength: Int, trailing: String? = "...") -> String`
|
|
|
|
|
|
Non-mutating truncation.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
"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`).
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
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).
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
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`).
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
"a.b.c".replace(from: ".", to: "-") // "a-b-c"
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### `func replacing(range: CountableClosedRange<Int>, with: String) -> String`
|
|
|
|
|
|
Replace by integer character range.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
"abcdef".replacing(range: 1...3, with: "X") // "aXef"
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### `func replacingLastOccurrenceOfString(_:with:caseInsensitive: Bool = true) -> String`
|
|
|
|
|
|
Replace only the last match.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
"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.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
"a1b2c3".replaceAll(of: "[0-9]", with: "#") // "a#b#c#"
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### `@discardableResult func replaceURL(_ withDict: [String: Any]) -> String`
|
|
|
|
|
|
Substitute `{key}` placeholders — used by `API.request(pathParams:)`.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
"/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
|
|
|
|
|
|
"Swift is amazing".words() // ["Swift", "is", "amazing"]
|
|
|
|
|
|
"Swift is amazing".wordCount() // 3
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### `func contains(_:caseSensitive: Bool = true) -> Bool`
|
|
|
|
|
|
Substring check with optional case-insensitivity.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
"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.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
"11987654321".applyMask(toText: "11987654321", mask: "(##) #####-####")
|
|
|
|
|
|
// "(11) 98765-4321"
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### `func exponentize(str: String) -> String`
|
|
|
|
|
|
Turn `^`-prefixed digits into Unicode superscripts.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
"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`.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
"".stringFromTimeInterval(3661.5) // "01:01:01.500"
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### `func toSlug() -> String`
|
|
|
|
|
|
Lowercase, de-accent, spaces → `-`, strip other punctuation.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
"Olá Mundo!".toSlug() // "ola-mundo"
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### `func localized(comment: String = "") -> String`
|
|
|
|
|
|
`NSLocalizedString(self, comment:)`.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
"welcome_title".localized()
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### Generators & misc
|
|
|
|
|
|
|
|
|
|
|
|
#### `static func loremIpsum(ofLength length: Int = 445) -> String`
|
|
|
|
|
|
Lorem-ipsum text truncated to `length` (max 445).
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
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.)
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
"".randomString(length: 8) // e.g. "a9Fk2Lp0"
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### `var JSONStringToDictionary: [String: Any]?`
|
|
|
|
|
|
Parse a JSON object string to a dictionary (`nil` + logs on failure).
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
#"{"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`.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
"<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.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
"Some label text".height(withConstrainedWidth: 200, font: .systemFont(ofSize: 14))
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
</details>
|
|
|
|
|
|
|
|
|
|
|
|
<details>
|
|
|
|
|
|
<summary><b>Character</b> — classification, conversion, repetition</summary>
|
|
|
|
|
|
|
|
|
|
|
|
### `var isEmoji: Bool`
|
|
|
|
|
|
True when the scalar falls in a known emoji range.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
Character("😀").isEmoji // true
|
|
|
|
|
|
Character("a").isEmoji // false
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### `var int: Int?` / `var string: String`
|
|
|
|
|
|
Digit value (or `nil`) / one-character `String`.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
Character("7").int // 7
|
|
|
|
|
|
Character("A").int // nil
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### `var lowercased: Character` / `var uppercased: Character`
|
|
|
|
|
|
Case-flipped character.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
Character("a").uppercased // "A"
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### `func unicodeScalarCodePoint() -> UInt32`
|
|
|
|
|
|
First Unicode scalar value.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
Character("A").unicodeScalarCodePoint() // 65
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### `static func randomAlphanumeric() -> Character`
|
|
|
|
|
|
Random `[A-Za-z0-9]` character.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
Character.randomAlphanumeric() // e.g. "k"
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### `static func * (Character, Int) -> String` / `static func * (Int, Character) -> String`
|
|
|
|
|
|
Repeat a character into a string.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
Character("-") * 5 // "-----"
|
|
|
|
|
|
5 * Character("-") // "-----"
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
</details>
|
|
|
|
|
|
|
|
|
|
|
|
<details>
|
|
|
|
|
|
<summary><b>NSString</b></summary>
|
|
|
|
|
|
|
|
|
|
|
|
### `var string: String?`
|
|
|
|
|
|
`String(describing:)` of the `NSString`.
|
|
|
|
|
|
|
|
|
|
|
|
### `func randomAlphaNumericString(_ length: Int = 8) -> String`
|
|
|
|
|
|
Random `[A-Za-z0-9]` string of the given length.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
("" as NSString).randomAlphaNumericString(12)
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
</details>
|
|
|
|
|
|
|
|
|
|
|
|
<details>
|
|
|
|
|
|
<summary><b>NSAttributedString / NSMutableAttributedString</b></summary>
|
|
|
|
|
|
|
|
|
|
|
|
### `NSAttributedString(html: String)` — *failable init*
|
|
|
|
|
|
Build an attributed string from an HTML fragment.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
|
|
|
|
</details>
|
|
|
|
|
|
|
|
|
|
|
|
<details>
|
|
|
|
|
|
<summary><b>Bytes</b> — <code>Sequence where Element == UInt8</code></summary>
|
|
|
|
|
|
|
|
|
|
|
|
### `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.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
let bytes: [UInt8] = [0x68, 0x69]
|
|
|
|
|
|
bytes.data // 2 bytes
|
|
|
|
|
|
bytes.string // "hi"
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
</details>
|
2026-08-29 19:22:12 -03:00
|
|
|
|
|
|
|
|
|
|
## Collections & Sequences
|
|
|
|
|
|
|
2026-08-29 19:26:46 -03:00
|
|
|
|
<details>
|
|
|
|
|
|
<summary><b>Array</b> — dedup, mutation helpers (<code>Element: Equatable</code>)</summary>
|
|
|
|
|
|
|
|
|
|
|
|
### `var unique: [Element]` / `func withoutDuplicates() -> [Element]`
|
|
|
|
|
|
New array with duplicates dropped, first occurrence kept (order preserved).
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
[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).
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
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`).
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
var a = [1, 2, 3]; a.safeSwap(from: 0, to: 2) // [3, 2, 1]
|
|
|
|
|
|
a.safeSwap(from: 0, to: 9) // unchanged
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
</details>
|
|
|
|
|
|
|
|
|
|
|
|
<details>
|
|
|
|
|
|
<summary><b>Collection</b> — safe indexing, chunking, indices, averages</summary>
|
|
|
|
|
|
|
|
|
|
|
|
### `var fullRange: Range<Index>`
|
|
|
|
|
|
`startIndex..<endIndex`.
|
|
|
|
|
|
|
|
|
|
|
|
### `subscript(safe index:) -> Element?` / `subscript(exist index:) -> Element?`
|
|
|
|
|
|
Bounds-checked access, `nil` instead of a crash.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
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`.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
[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.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
[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.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
[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`.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
[1, 2, 3, 4].average() // 2.5
|
|
|
|
|
|
[1.2, 2.3, 4.5].average() // 2.666…
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
</details>
|
|
|
|
|
|
|
|
|
|
|
|
<details>
|
|
|
|
|
|
<summary><b>BidirectionalCollection</b></summary>
|
|
|
|
|
|
|
|
|
|
|
|
### `subscript(offset distance: Int) -> Element`
|
|
|
|
|
|
Positive offset from the start, negative from the end.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
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`.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
events.last(where: \.type, equals: .login)
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
</details>
|
|
|
|
|
|
|
|
|
|
|
|
<details>
|
|
|
|
|
|
<summary><b>Sequence</b> — predicates, key-path sorting, sums, dedup</summary>
|
|
|
|
|
|
|
|
|
|
|
|
### `func all(matching:)` / `func none(matching:)` / `func any(matching:)`
|
|
|
|
|
|
Whether the predicate holds for all / no / at least one element.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
[2, 4, 6].all(matching: { $0 % 2 == 0 }) // true
|
|
|
|
|
|
[1, 3].any(matching: { $0 % 2 == 0 }) // false
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### `func reject(where:) -> [Element]`
|
|
|
|
|
|
Inverse of `filter`.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
[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.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
[1, 2, 3].accumulate(initial: 0, next: +) // [1, 3, 6]
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### `func filtered<T>(_ isIncluded:, map transform:) -> [T]`
|
|
|
|
|
|
`filter` + `map` in one lazy pass.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
[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.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
[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.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
[(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.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
[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.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
[1, 2, 2, 3, 3].duplicates().sorted() // [2, 3]
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
</details>
|
|
|
|
|
|
|
|
|
|
|
|
<details>
|
|
|
|
|
|
<summary><b>RangeReplaceableCollection</b> — rotate, take/skip, offset subscripts</summary>
|
|
|
|
|
|
|
|
|
|
|
|
### `init(expression:count:)`
|
|
|
|
|
|
Build a collection by evaluating an autoclosure `count` times.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
[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.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
[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.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
var a = [1]; a.appendIfNonNil(Optional<Int>.none) // [1]
|
|
|
|
|
|
a.appendIfNonNil(2) // [1, 2]
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
</details>
|
|
|
|
|
|
|
|
|
|
|
|
<details>
|
|
|
|
|
|
<summary><b>Dictionary</b> — key paths, JSON, key/value maps, merge operators</summary>
|
|
|
|
|
|
|
|
|
|
|
|
### `subscript(path path: [Key]) -> Any?`
|
|
|
|
|
|
Deep get/set through nested dictionaries.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
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).
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
["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.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
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 `-` / `-=`.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
["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.
|
|
|
|
|
|
|
|
|
|
|
|
</details>
|
|
|
|
|
|
|
|
|
|
|
|
<details>
|
|
|
|
|
|
<summary><b>Optional</b> — safe unwrap, conditional assignment</summary>
|
|
|
|
|
|
|
|
|
|
|
|
### `func unwrapped(or defaultValue: Wrapped) -> Wrapped`
|
|
|
|
|
|
`self ?? defaultValue`, read nicely.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
let name: String? = nil
|
|
|
|
|
|
name.unwrapped(or: "Guest") // "Guest"
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### `func unwrapped(or error: Error) throws -> Wrapped`
|
|
|
|
|
|
Unwrap or throw a chosen error.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
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).
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
token.run { print("have token \($0)") }
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### `static func ??= (lhs: inout Optional, rhs: Optional)`
|
|
|
|
|
|
Assign only if the right side is non-nil.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
var params: String? = "a"; params ??= nil // still "a"
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### `static func ?= (lhs: inout Optional, rhs: @autoclosure)`
|
|
|
|
|
|
Assign only if the left side is currently nil.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
let status: Status? = .active
|
|
|
|
|
|
status == "active" // true
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
</details>
|
|
|
|
|
|
|
|
|
|
|
|
<details>
|
|
|
|
|
|
<summary><b>Comparable</b></summary>
|
|
|
|
|
|
|
|
|
|
|
|
### `func isBetween(_ range: ClosedRange<Self>) -> Bool`
|
|
|
|
|
|
Range membership.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
7.isBetween(6...12) // true
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### `func clamped(to range: ClosedRange<Self>) -> Self`
|
|
|
|
|
|
Constrain a value to a range.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
1.clamped(to: 3...8) // 3
|
|
|
|
|
|
0.32.clamped(to: 0.1...0.29) // 0.29
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
</details>
|
|
|
|
|
|
|
|
|
|
|
|
<details>
|
|
|
|
|
|
<summary><b>Bool</b></summary>
|
|
|
|
|
|
|
|
|
|
|
|
### `var int: Int` / `var string: String` / `var data: Data`
|
|
|
|
|
|
`1`/`0`, `"true"`/`"false"`, or a single-byte `Data`.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
true.int // 1
|
|
|
|
|
|
false.string // "false"
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
</details>
|
2026-08-29 19:22:12 -03:00
|
|
|
|
|
|
|
|
|
|
## Numbers & Geometry
|
|
|
|
|
|
|
2026-08-29 19:27:31 -03:00
|
|
|
|
<details>
|
|
|
|
|
|
<summary><b>Int</b> — conversions, digits, primes, roman numerals, operators</summary>
|
|
|
|
|
|
|
|
|
|
|
|
### `var double: Double` / `var float: Float` / `var cgFloat: CGFloat` / `var uInt: UInt` / `var uInt32: UInt32` / `var uInt64: UInt64`
|
|
|
|
|
|
Straight numeric conversions (`uInt32`/`uInt64` truncate).
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
5.double // 5.0
|
|
|
|
|
|
(-1).uInt32 // 4294967295
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### `var countableRange: CountableRange<Int>`
|
|
|
|
|
|
`0..<self`.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
3.countableRange // 0..<3
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### `var degreesToRadians: Double` / `var radiansToDegrees: Double`
|
|
|
|
|
|
Angle conversion.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
180.degreesToRadians // 3.14159…
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### `var digits: [Int]` / `var digitsCount: Int`
|
|
|
|
|
|
Decimal digits of `abs(self)`, and how many.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
1234.digits // [1, 2, 3, 4]
|
|
|
|
|
|
1234.digitsCount // 4
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### `var kFormatted: String`
|
|
|
|
|
|
Compact "k"/"kk" formatting for values ≥ 1000.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
5300.kFormatted // "5k"
|
|
|
|
|
|
2_500_000.kFormatted // "25kk"
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### `var timestampToDate: Date`
|
|
|
|
|
|
`Date(timeIntervalSince1970:)`.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
150_000_000.satsToBTC // "1.50000000"
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### `func isPrime() -> Bool`
|
|
|
|
|
|
Primality test (trial division up to √n).
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
7.isPrime() // true
|
|
|
|
|
|
9.isPrime() // false
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### `func romanNumeral() -> String?`
|
|
|
|
|
|
Roman numerals for positive integers, `nil` for 0 or negative.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
2024.romanNumeral() // "MMXXIV"
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### `func roundToNearest(_ number: Int) -> Int`
|
|
|
|
|
|
Round to the closest multiple of `number`.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
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)` |
|
|
|
|
|
|
|
|
|
|
|
|
</details>
|
|
|
|
|
|
|
|
|
|
|
|
<details>
|
|
|
|
|
|
<summary><b>Float / Double</b></summary>
|
|
|
|
|
|
|
|
|
|
|
|
### `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`).
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
150_000_000.0.satsToBTC // 1.5
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### `func rounded(toPlaces places: Int) -> Float` — *(Float only)*
|
|
|
|
|
|
Round to N decimal places.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
Float(3.14159).rounded(toPlaces: 2) // 3.14
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### Operator `a ** b`
|
|
|
|
|
|
Exponentiation, staying in the same type (`Float ** Float → Float`, `Double ** Double → Double`).
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
4.4 ** 0.5 // 2.0976…
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
</details>
|
|
|
|
|
|
|
|
|
|
|
|
<details>
|
|
|
|
|
|
<summary><b>Decimal</b></summary>
|
|
|
|
|
|
|
|
|
|
|
|
### `mutating func round(_ scale: Int, _ roundingMode:)` / `func rounded(_ scale:, _ roundingMode:) -> Decimal`
|
|
|
|
|
|
Decimal rounding via `NSDecimalRound` — mutating and non-mutating.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
Decimal(2.567).rounded(2, .plain) // 2.57
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
</details>
|
|
|
|
|
|
|
|
|
|
|
|
<details>
|
|
|
|
|
|
<summary><b>BinaryInteger</b></summary>
|
|
|
|
|
|
|
|
|
|
|
|
### `var bytes: [UInt8]`
|
|
|
|
|
|
Big-endian raw byte representation.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
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).
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
Int16(bytes: [0xFF, 0xFD]) // -3
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
</details>
|
|
|
|
|
|
|
|
|
|
|
|
<details>
|
|
|
|
|
|
<summary><b>BinaryFloatingPoint</b></summary>
|
|
|
|
|
|
|
|
|
|
|
|
### `func rounded(numberOfDecimalPlaces: Int, rule: FloatingPointRoundingRule) -> Self`
|
|
|
|
|
|
Round to N places with an explicit rule (negative places treated as 0).
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
3.1415927.rounded(numberOfDecimalPlaces: 3, rule: .up) // 3.142
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
</details>
|
|
|
|
|
|
|
|
|
|
|
|
<details>
|
|
|
|
|
|
<summary><b>SignedNumeric</b></summary>
|
|
|
|
|
|
|
|
|
|
|
|
### `var string: String`
|
|
|
|
|
|
`String(describing:)`.
|
|
|
|
|
|
|
|
|
|
|
|
### `var asLocaleCurrency: String?` / `func asCurrency(locale: Locale = pt_BR) -> String?`
|
|
|
|
|
|
Currency formatting in the current locale / a specified locale.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
92.spelledOutString(locale: Locale(identifier: "en")) // "ninety-two"
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
</details>
|
|
|
|
|
|
|
|
|
|
|
|
<details>
|
|
|
|
|
|
<summary><b>CGFloat</b></summary>
|
|
|
|
|
|
|
|
|
|
|
|
### `var abs / ceil / floor` / `var int / float / double`
|
|
|
|
|
|
Math and numeric conversions.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
|
|
|
|
</details>
|
|
|
|
|
|
|
|
|
|
|
|
<details>
|
|
|
|
|
|
<summary><b>CGRect</b></summary>
|
|
|
|
|
|
|
|
|
|
|
|
### `var center: CGPoint`
|
|
|
|
|
|
Rect centre.
|
|
|
|
|
|
|
|
|
|
|
|
### `init(center: CGPoint, size: CGSize)`
|
|
|
|
|
|
Build a rect from its centre and size.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
rect.resizing(to: CGSize(width: 100, height: 100), anchor: CGPoint(x: 0, y: 1))
|
|
|
|
|
|
// grows from the bottom-left corner
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
</details>
|
|
|
|
|
|
|
|
|
|
|
|
<details>
|
|
|
|
|
|
<summary><b>CGSize</b></summary>
|
|
|
|
|
|
|
|
|
|
|
|
### `var aspectRatio: CGFloat` / `var maxDimension: CGFloat` / `var minDimension: CGFloat`
|
|
|
|
|
|
`width / height` (0 when height is 0), and the larger / smaller side.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
CGSize(width: 120, height: 80).aspectFit(to: CGSize(width: 100, height: 50))
|
|
|
|
|
|
// 75 × 50
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### Operators
|
|
|
|
|
|
`+`, `-`, `*` and their `+=`/`-=`/`*=` forms, between two `CGSize`s, a `CGSize`
|
|
|
|
|
|
and a `(width, height)` tuple, or a `CGSize` and a scalar.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
CGSize(width: 5, height: 10) + CGSize(width: 3, height: 4) // 8 × 14
|
|
|
|
|
|
CGSize(width: 5, height: 10) * 3 // 15 × 30
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
</details>
|
|
|
|
|
|
|
|
|
|
|
|
<details>
|
|
|
|
|
|
<summary><b>CGPoint / CGRect / CGSize — <code>Hashable</code></summary>
|
|
|
|
|
|
|
|
|
|
|
|
When SwiftUI is available, `CGPoint`, `CGRect`, and `CGSize` are made
|
|
|
|
|
|
`Hashable` (retroactive conformance) so they can be used as dictionary keys or
|
|
|
|
|
|
in `Set`s and as SwiftUI identifiers.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
var seen: Set<CGPoint> = []
|
|
|
|
|
|
seen.insert(CGPoint(x: 1, y: 2))
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
</details>
|
2026-08-29 19:22:12 -03:00
|
|
|
|
|
|
|
|
|
|
## Date, Data & Files
|
|
|
|
|
|
|
|
|
|
|
|
<!-- batch 6 -->
|
|
|
|
|
|
|
|
|
|
|
|
## Encoding & Errors
|
|
|
|
|
|
|
|
|
|
|
|
<!-- batch 7 -->
|
|
|
|
|
|
|
|
|
|
|
|
## Crypto
|
|
|
|
|
|
|
2026-08-29 19:23:23 -03:00
|
|
|
|
<details>
|
|
|
|
|
|
<summary><b>RIPEMD_160</b> — RIPEMD-160 digest</summary>
|
|
|
|
|
|
|
|
|
|
|
|
### `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))`).
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
let digest = RIPEMD_160.hash(Data("abc".utf8))
|
|
|
|
|
|
digest.count // 20
|
|
|
|
|
|
digest.map { String(format: "%02x", $0) }.joined()
|
|
|
|
|
|
// "8eb208f7e05d987a9b044a8e98c6b087f15a0bfc"
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
</details>
|
|
|
|
|
|
|
|
|
|
|
|
<details>
|
|
|
|
|
|
<summary><b>LCECryptoKitManager</b> — OTP / peppered-login bridge (needs the <code>LCECryptoKit</code> binary)</summary>
|
|
|
|
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
let crypto = LCECryptoKitManager()
|
|
|
|
|
|
let keyed = LCECryptoKitManager(privateKey: serverHashKey)
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### `static func generateKey() -> String`
|
|
|
|
|
|
|
|
|
|
|
|
Generates a random AES key string.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
|
|
|
|
```swift
|
|
|
|
|
|
let enc = LCECryptoKitManager.otpEncode("secret-value")
|
|
|
|
|
|
let dec = LCECryptoKitManager.otpDecode(enc ?? "") // "secret-value"
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
</details>
|
2026-08-29 19:22:12 -03:00
|
|
|
|
|
|
|
|
|
|
## Core — the `LCEssentials` namespace
|
|
|
|
|
|
|
|
|
|
|
|
<!-- batch 7 -->
|