Loading...
Loading...
Apply modern Swift language patterns and idioms for non-concurrency, non-SwiftUI code. Covers if/switch expressions (Swift 5.9+), typed throws (Swift 6+), result builders, property wrappers, opaque and existential types (some vs any), guard patterns, Never type, Regex builders (Swift 5.7+), Codable best practices (CodingKeys, custom decoding, nested containers), modern collection APIs (count(where:), contains(where:), replacing()), FormatStyle (.formatted() on dates, numbers, measurements), and string interpolation patterns. Use when writing core Swift code involving generics, protocols, enums, closures, or modern language features.
npx skill4agent add dpearson2699/swift-ios-skills swift-languageswift-concurrencyswiftui-patternsifswitch// Assign from if expression
let icon = if isComplete { "checkmark.circle.fill" } else { "circle" }
// Assign from switch expression
let label = switch status {
case .draft: "Draft"
case .published: "Published"
case .archived: "Archived"
}
// Works in return position
func color(for priority: Priority) -> Color {
switch priority {
case .high: .red
case .medium: .orange
case .low: .green
}
}enum ValidationError: Error {
case tooShort, invalidCharacters, alreadyTaken
}
func validate(username: String) throws(ValidationError) -> String {
guard username.count >= 3 else { throw .tooShort }
guard username.allSatisfy(\.isLetterOrDigit) else { throw .invalidCharacters }
return username.lowercased()
}
// Caller gets typed error -- no cast needed
do {
let name = try validate(username: input)
} catch {
// error is ValidationError, not any Error
switch error {
case .tooShort: print("Too short")
case .invalidCharacters: print("Invalid characters")
case .alreadyTaken: print("Taken")
}
}throws(SomeError)throwsthrows(Never)throws(A)throws(B)throws@resultBuilder@ViewBuilder@resultBuilder
struct ArrayBuilder<Element> {
static func buildBlock(_ components: [Element]...) -> [Element] {
components.flatMap { $0 }
}
static func buildExpression(_ expression: Element) -> [Element] { [expression] }
static func buildOptional(_ component: [Element]?) -> [Element] { component ?? [] }
static func buildEither(first component: [Element]) -> [Element] { component }
static func buildEither(second component: [Element]) -> [Element] { component }
static func buildArray(_ components: [[Element]]) -> [Element] { components.flatMap { $0 } }
}
func makeItems(@ArrayBuilder<String> content: () -> [String]) -> [String] { content() }
let items = makeItems {
"Always included"
if showExtra { "Conditional" }
for name in names { name.uppercased() }
}buildBlockbuildExpressionbuildOptionalifelsebuildEitherif/elsebuildArrayfor..inbuildFinalResult@propertyWrapper@propertyWrapper
struct Clamped<Value: Comparable> {
private var value: Value
let range: ClosedRange<Value>
var wrappedValue: Value {
get { value }
set { value = min(max(newValue, range.lowerBound), range.upperBound) }
}
var projectedValue: ClosedRange<Value> { range }
init(wrappedValue: Value, _ range: ClosedRange<Value>) {
self.range = range
self.value = min(max(wrappedValue, range.lowerBound), range.upperBound)
}
}
// Usage
struct Volume {
@Clamped(0...100) var level: Int = 50
}
var v = Volume()
v.level = 150 // clamped to 100
print(v.$level) // projected value: 0...100wrappedValueprojectedValue$property@A @B var xsome Protocolfunc makeCollection() -> some Collection<Int> {
[1, 2, 3] // Always returns Array<Int> -- compiler knows the concrete type
}somefunc process(_ items: some Collection<Int>)<C: Collection<Int>>any Protocolfunc process(items: [any StringProtocol]) {
for item in items {
print(item.uppercased())
}
}Use | Use |
|---|---|
| Return type hiding concrete type | Heterogeneous collections |
| Function parameters (replaces simple generics) | Dynamic type erasure needed |
| Better performance (static dispatch) | Protocol has |
someanyguardfunc processOrder(_ order: Order?) throws -> Receipt {
// Unwrap optionals
guard let order else { throw OrderError.missing }
// Validate conditions
guard order.items.isEmpty == false else { throw OrderError.empty }
guard order.total > 0 else { throw OrderError.invalidTotal }
// Boolean checks
guard order.isPaid else { throw OrderError.unpaid }
// Pattern matching
guard case .confirmed(let date) = order.status else {
throw OrderError.notConfirmed
}
return Receipt(order: order, confirmedAt: date)
}guardifguard let a, let b else { return }elsereturnthrowcontinuebreakfatalError()guard let value else { ... }Never// Function that terminates the program
func crashWithDiagnostics(_ message: String) -> Never {
let diagnostics = gatherDiagnostics()
logger.critical("\(message): \(diagnostics)")
fatalError(message)
}
// Useful in generic contexts
enum Result<Success, Failure: Error> {
case success(Success)
case failure(Failure)
}
// Result<String, Never> -- a result that can never fail
// Result<Never, Error> -- a result that can never succeed
// Exhaustive switch: no default needed since Never has no cases
func handle(_ result: Result<String, Never>) {
switch result {
case .success(let value): print(value)
// No .failure case needed -- compiler knows it's impossible
}
}import RegexBuilder
// Parse "2024-03-15" into components
let dateRegex = Regex {
Capture { /\d{4}/ }; "-"; Capture { /\d{2}/ }; "-"; Capture { /\d{2}/ }
}
if let match = "2024-03-15".firstMatch(of: dateRegex) {
let (_, year, month, day) = match.output
}
// TryCapture with transform
let priceRegex = Regex {
"$"
TryCapture { OneOrMore(.digit); "."; Repeat(.digit, count: 2) }
transform: { Decimal(string: String($0)) }
}/pattern//.../struct User: Codable {
let id: Int
let displayName: String
let avatarURL: URL
enum CodingKeys: String, CodingKey {
case id
case displayName = "display_name"
case avatarURL = "avatar_url"
}
}struct Item: Decodable {
let name: String
let quantity: Int
let isActive: Bool
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
name = try container.decode(String.self, forKey: .name)
quantity = try container.decodeIfPresent(Int.self, forKey: .quantity) ?? 0
if let boolValue = try? container.decode(Bool.self, forKey: .isActive) {
isActive = boolValue
} else {
isActive = (try container.decode(String.self, forKey: .isActive)).lowercased() == "true"
}
}
enum CodingKeys: String, CodingKey { case name, quantity; case isActive = "is_active" }
}// JSON: { "id": 1, "metadata": { "created_at": "...", "tags": [...] } }
struct Record: Decodable {
let id: Int
let createdAt: String
let tags: [String]
enum CodingKeys: String, CodingKey {
case id, metadata
}
enum MetadataKeys: String, CodingKey {
case createdAt = "created_at"
case tags
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(Int.self, forKey: .id)
let metadata = try container.nestedContainer(
keyedBy: MetadataKeys.self, forKey: .metadata)
createdAt = try metadata.decode(String.self, forKey: .createdAt)
tags = try metadata.decode([String].self, forKey: .tags)
}
}references/swift-patterns-extended.mdlet numbers = [1, 2, 3, 4, 5, 6, 7, 8]
// count(where:) -- Swift 5.0+, use instead of .filter { }.count
let evenCount = numbers.count(where: { $0.isMultiple(of: 2) })
// contains(where:) -- short-circuits on first match
let hasNegative = numbers.contains(where: { $0 < 0 })
// first(where:) / last(where:)
let firstEven = numbers.first(where: { $0.isMultiple(of: 2) })
// String replacing() -- Swift 5.7+, returns new string
let cleaned = rawText.replacing(/\s+/, with: " ")
let snakeCase = name.replacing("_", with: " ")
// compactMap -- unwrap optionals from a transform
let ids = strings.compactMap { Int($0) }
// flatMap -- flatten nested collections
let allTags = articles.flatMap(\.tags)
// Dictionary(grouping:by:)
let byCategory = Dictionary(grouping: items, by: \.category)
// reduce(into:) -- efficient accumulation
let freq = words.reduce(into: [:]) { counts, word in
counts[word, default: 0] += 1
}.formatted()DateFormatterNumberFormatter// Dates
let now = Date.now
now.formatted() // "3/15/2024, 2:30 PM"
now.formatted(date: .abbreviated, time: .shortened) // "Mar 15, 2024, 2:30 PM"
now.formatted(.dateTime.year().month().day()) // "Mar 15, 2024"
now.formatted(.relative(presentation: .named)) // "yesterday"
// Numbers
let price = 42.5
price.formatted(.currency(code: "USD")) // "$42.50"
price.formatted(.percent) // "4,250%"
(1_000_000).formatted(.number.notation(.compactName)) // "1M"
// Measurements
let distance = Measurement(value: 5, unit: UnitLength.kilometers)
distance.formatted(.measurement(width: .abbreviated)) // "5 km"
// Duration (Swift 5.7+)
let duration = Duration.seconds(3661)
duration.formatted(.time(pattern: .hourMinuteSecond)) // "1:01:01"
// Byte counts
Int64(1_500_000).formatted(.byteCount(style: .file)) // "1.5 MB"
// Lists
["Alice", "Bob", "Carol"].formatted(.list(type: .and)) // "Alice, Bob, and Carol"FormatStylelet value = try Decimal("$42.50", format: .currency(code: "USD"))
let date = try Date("Mar 15, 2024", strategy: .dateTime.month().day().year())DefaultStringInterpolation""""""references/swift-patterns-extended.mdanysomesomeanycount(where:)contains(where:)compactMapflatMapDateFormatter.formatted()decodeIfPresentguard let.replacing().contains()varappendmapfiltercompactMapreduce(into:)varsomeanyguardif let.formatted()DateFormatterNumberFormatterCodingKeysdecodeIfPresentString(describing:)Neverreferences/swift-patterns-extended.md