22 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
Numbers & Geometry
Date, Data & Files
Encoding & Errors
Crypto
RIPEMD_160 — RIPEMD-160 digest
static func hash(_ message: Data) -> Data
Returns the 20-byte RIPEMD-160 digest of message. Pure Swift, no system
dependency. Mainly useful for Bitcoin-style address hashing (RIPEMD160(SHA256(x))).
let digest = RIPEMD_160.hash(Data("abc".utf8))
digest.count // 20
digest.map { String(format: "%02x", $0) }.joined()
// "8eb208f7e05d987a9b044a8e98c6b087f15a0bfc"
LCECryptoKitManager — OTP / peppered-login bridge (needs the LCECryptoKit binary)
A thin facade over the optional LCECryptoKit binary product (enabled by the
LCE_ENABLE_CRYPTO_BINARY build flag). When the binary is not linked every
method is a no-op returning nil / "" / false, so calling code still
compiles and runs.
init() / init(privateKey:)
Create a manager. The privateKey (a.k.a. "hash key") is only needed by the
*WithKey methods.
let crypto = LCECryptoKitManager()
let keyed = LCECryptoKitManager(privateKey: serverHashKey)
static func generateKey() -> String
Generates a random AES key string.
let key = LCECryptoKitManager.generateKey()
func encodeTP(email:password:) -> String? / func decodeOTP(_:) -> String?
Encode an email+password pair into an OTP seed hash, and decode it back.
let hash = crypto.encodeTP(email: "ana@x.com", password: "s3cr3t")
let back = crypto.decodeOTP(hash ?? "")
func encodeOTPWithKey(email:password:) -> String? / func decodeOTPWithKey(_:) -> Bool
Same as above but bound to the instance's privateKey; decodeOTPWithKey
returns whether the hash validates against that key rather than the decoded value.
let keyed = LCECryptoKitManager(privateKey: serverHashKey)
let hash = keyed.encodeOTPWithKey(email: "ana@x.com", password: "s3cr3t")
let ok = keyed.decodeOTPWithKey(hash ?? "") // Bool
static func generateSalt() -> String
Random salt for the salted/iterated/peppered login flow.
let salt = LCECryptoKitManager.generateSalt()
static func computeClientHash(email:password:salt:) -> String
Client-side hash of the credentials with the given salt — sent to the server instead of the raw password.
let clientHash = LCECryptoKitManager.computeClientHash(
email: "ana@x.com", password: "s3cr3t", salt: salt
)
static func computeLoginBearerToken(userId:clientHash:) -> String?
Derives the login bearer token from a user id and the client hash.
let token = LCECryptoKitManager.computeLoginBearerToken(userId: "42", clientHash: clientHash)
static func otpEncode(_:) -> String? / static func otpDecode(_:) -> String?
One-time-pad encode/decode of an arbitrary string.
let enc = LCECryptoKitManager.otpEncode("secret-value")
let dec = LCECryptoKitManager.otpDecode(enc ?? "") // "secret-value"