What is Swift?
Swift is Apple's modern, open-source programming language designed for safety, speed, and expressiveness. Released in 2014, it replaced Objective-C as the primary language for iOS, macOS, watchOS, tvOS, and now server-side Linux apps. It combines ideas from C, Python, Rust, Haskell, and Ruby into a cohesive, statically-typed language.
Type Safe
Catches type errors at compile time, eliminating entire classes of bugs.
Fast
Compiles to native machine code; benchmark speed rivals C++.
Memory Safe
ARC (Automatic Reference Counting) handles memory without GC pauses.
Protocol-Oriented
Prefer protocols and value types over class hierarchies.
Optionals
Null safety is built into the type system — no null pointer exceptions.
Open Source
Runs on Linux and Windows via Swift.org toolchain.
Installation
| Platform | Method | REPL |
|---|---|---|
| macOS | Install Xcode from the App Store (includes Swift) | swift repl |
| Linux | Download toolchain from swift.org/download | swift repl |
| Windows | Swift for Windows installer at swift.org | swift repl |
| Online | swiftfiddle.com or Swift Playgrounds (iPad/Mac) | — |
Running a Swift file
# Compile + run in one step (script mode)
swift hello.swift
# Or compile to a binary
swiftc hello.swift -o hello
./hello
# Interactive REPL
swift repl
Swift Package Manager (SPM) is the official build system for multi-file projects. Run swift package init --type executable to scaffold a new project.
Syntax Reference
If you know Python, JavaScript, Kotlin, or C#, Swift will feel immediately familiar. Here's a one-page orientation for those switching languages.
| Concept | Swift | Equivalent (Python / JS / C++) |
|---|---|---|
| Variable | var x = 10 | x = 10 / let x = 10 / int x = 10 |
| Constant | let PI = 3.14 | PI = 3.14 / const PI / const double |
| String interpolation | "Hello \(name)" | f"Hello {name}" / `Hello ${name}` |
print("hi") | print("hi") / console.log / cout | |
| Function | func add(_ a: Int, _ b: Int) -> Int | def / function / int |
| Array | [1, 2, 3] | same across languages |
| Dictionary | ["key": "value"] | {} dict / Map / unordered_map |
| For loop | for i in 1...5 { } | for i in range(6) / for(let i=1;i<=5;i++) |
| If / else | if x > 0 { } else { } | same (Swift drops the parens) |
| Nil / null | nil | None / null / nullptr |
| Optional type | var s: String? | No direct equivalent in C++/Java |
| Class | class Dog { } | same |
| Struct | struct Point { } (value type) | Same keyword; Swift structs are powerful |
| Protocol | protocol Drawable { } | interface / ABC / concept |
| Enum | enum Dir { case north, south } | Similar; Swift enums can hold data |
| Guard clause | guard let x = opt else { return } | Early-return pattern |
| Type annotation | var score: Int = 0 | score: int = 0 (Python 3.6+) |
| Semicolons | Optional (omitted by convention) | Required in C/Java; optional in JS |
| No main() needed | Scripts run top-to-bottom; apps use @main | Like Python scripts |
Swift does not require parentheses around if/while conditions, but does require curly braces { } around every block — even single-line ones.
Hello, World!
The simplest Swift program — save as hello.swift and run with swift hello.swift.
// hello.swift — the classic first program
print("Hello, World!")
That's it. No main(), no imports, no semicolons required. Now a slightly richer version:
// hello2.swift — string interpolation & multi-print
let language = "Swift"
let version: Double = 5.9
print("Hello from \(language) \(version)!")
print("2 + 2 = \(2 + 2)")
print("π ≈ \(Double.pi)")
Output:
Hello from Swift 5.9!
2 + 2 = 4
π ≈ 3.141592653589793
print() appends a newline by default. Use print("text", terminator: "") to suppress it.
Variables & Constants
var declares a mutable variable. let declares an immutable constant. Prefer let — the compiler warns you when a var is never mutated.
var score: Int = 0 // mutable, explicit type
score = 10 // reassignment OK
let maxScore = 100 // immutable; type inferred as Int
// maxScore = 99 // ❌ ERROR — cannot reassign a let
var name = "Alice" // String inferred
var temp: Double = 98.6
var isActive: Bool = true
// Multiple assignment
var (x, y) = (10, 20)
Naming conventions
- camelCase for variables, functions, and properties:
myVariable,calculateArea() - PascalCase for types:
Int,String,MyClass - Any Unicode is valid:
let 🐦 = "swift"(though not recommended)
Types & Type Inference
Swift is statically typed but rarely requires you to write types explicitly, thanks to type inference.
| Type | Literal | Notes |
|---|---|---|
Int | 42 | Platform-native (64-bit on modern hardware). Also: Int8/16/32/64, UInt |
Double | 3.14 | 64-bit float. Preferred over Float (32-bit) |
Bool | true / false | No integer conversion (0 ≠ false) |
String | "Hello" | Unicode-correct, value type |
Character | "A" | Single Unicode grapheme cluster |
Array<T> | [1, 2, 3] | Shorthand: [Int] |
Dictionary<K,V> | ["a": 1] | Shorthand: [String: Int] |
Set<T> | Set([1, 2, 3]) | Unordered, unique elements |
Tuple | (1, "hi") | Fixed-size, heterogeneous |
Optional<T> | nil | Shorthand: String? |
// Type inference
let age = 25 // Int
let price = 9.99 // Double
let greeting = "Hello" // String
// Explicit annotation overrides inference
let pi: Float = 3.14 // Float, not Double
// Type conversion (no implicit casting!)
let intVal: Int = 5
let dblVal = Double(intVal) * 1.5 // must cast explicitly
// Tuples
let point = (x: 3, y: 7)
print(point.x) // 3
// Type aliases
typealias Celsius = Double
var bodyTemp: Celsius = 37.0
Swift has no implicit type coercion. You cannot add an Int to a Double without an explicit cast — this is intentional to prevent precision bugs.
Optionals: Swift's Superpower
An Optional is a type that can hold either a value or nil (absence of value). It is impossible in Swift to accidentally use nil — you must explicitly unwrap optionals first.
// Declaring optionals
var username: String? // = nil by default
username = "Bob"
// ── 1. Optional Binding (safe) ──────────────────────
if let name = username {
print("Hello, \(name)") // name is String here
} else {
print("No user")
}
// Shorthand (same name): Swift 5.7+
if let username {
print("Got \(username)")
}
// ── 2. Guard Let (early exit pattern) ──────────────
func greet(user: String?) {
guard let user else {
print("No user provided")
return
}
print("Welcome, \(user)!") // user is non-optional here
}
// ── 3. Nil Coalescing ─────────────────────────────
let display = username ?? "Guest" // "Bob" or "Guest"
// ── 4. Optional Chaining ──────────────────────────
let count = username?.count // Int? (nil if username is nil)
// ── 5. Force Unwrap (use sparingly) ──────────────
let definitelyString = username! // crashes if nil!
Force unwrapping (!) crashes at runtime if the optional is nil. Use if let or guard let instead. Reserve ! only when you are 100% certain the value exists.
Operators
| Category | Operators | Notes |
|---|---|---|
| Arithmetic | + - * / % | Standard. % is remainder, not modulo |
| Assignment | = += -= *= /= | Note: ++ and -- were removed in Swift 3 |
| Comparison | == != < > <= >= | Works for any Equatable / Comparable |
| Logical | && || ! | Short-circuit evaluation |
| Range | 1...5 1..<5 | Closed vs half-open. Very common in loops |
| Nil coalescing | ?? | a ?? b → a if non-nil, else b |
| Ternary | a ? b : c | Same as C/Java |
| Identity | === !== | Reference equality (classes only) |
| Bitwise | & | ^ ~ << >> | Same as C |
// Range operators
for i in 1...5 { } // 1, 2, 3, 4, 5 (closed)
for i in 1..<5 { } // 1, 2, 3, 4 (half-open)
// Overflow operators (safe wrapping arithmetic)
let max = UInt8.max // 255
let wrapped = max &+ 1 // 0 (wraps)
// Operator overloading (custom types)
struct Vec2 {
var x, y: Double
static func + (a: Vec2, b: Vec2) -> Vec2 {
Vec2(x: a.x + b.x, y: a.y + b.y)
}
}
Control Flow
If / Else if / Else
let temp = 72
if temp < 50 {
print("Cold")
} else if temp < 75 {
print("Comfortable")
} else {
print("Hot")
}
Switch
Swift's switch is far more powerful than C's. It does not fall through by default and works with any type.
let grade = "B"
switch grade {
case "A":
print("Excellent")
case "B", "C":
print("Good / Average")
case "D"..."F": // range matching
print("Needs work")
default:
print("Unknown grade")
}
// switch with where clause
let num = 42
switch num {
case let n where n < 0: print("Negative")
case let n where n.isMultiple(of: 2): print("Even: \(n)")
default: print("Odd")
}
Loops
// for-in with range
for i in 1...5 {
print(i)
}
// for-in over collection
let fruits = ["apple", "banana", "cherry"]
for fruit in fruits { print(fruit) }
// enumerated (index + value)
for (i, fruit) in fruits.enumerated() {
print("\(i): \(fruit)")
}
// while
var count = 3
while count > 0 { count -= 1 }
// repeat-while (do-while in other languages)
repeat {
print("runs at least once")
} while false
// Control keywords
break // exit loop or switch
continue // skip to next iteration
fallthrough // opt into switch fall-through
// Labeled loops
outer: for i in 0...3 {
for j in 0...3 {
if i == j { break outer } // breaks both loops
}
}
Functions
Functions in Swift have argument labels (external name) and parameter names (internal name) — a uniquely Swift concept that makes call sites read like natural English.
// Basic function
func greet(name: String) -> String {
return "Hello, \(name)!"
}
greet(name: "Alice") // label required at call site
// Omit external label with _
func square(_ n: Int) -> Int { n * n }
square(5) // no label needed
// Separate external & internal label
func move(to destination: String) {
print("Moving to \(destination)")
}
move(to: "Paris") // reads like English
// Default parameter values
func createTag(name: String, color: String = "gray") -> String {
"<\(name) color='\(color)'>"
}
createTag(name: "div") // uses "gray"
createTag(name: "div", color: "red")
// Multiple return values via tuple
func minMax(of array: [Int]) -> (min: Int, max: Int) {
(array.min()!, array.max()!)
}
let result = minMax(of: [3, 1, 7, 2])
print(result.min) // 1
// Variadic parameters
func sum(_ numbers: Int...) -> Int {
numbers.reduce(0, +)
}
sum(1, 2, 3, 4) // 10
// inout parameters (pass by reference)
func doubleValue(_ n: inout Int) { n *= 2 }
var x = 5
doubleValue(&x) // x is now 10
// Functions as first-class values
let fn: (Int) -> Int = square
print(fn(6)) // 36
Single-expression functions
If the function body is a single expression, you can omit return:
func cube(_ n: Int) -> Int { n * n * n }
Closures
Closures are self-contained blocks of functionality — like anonymous functions (lambdas). They can capture variables from their surrounding context.
// Full closure syntax
let add: (Int, Int) -> Int = { (a: Int, b: Int) -> Int in
return a + b
}
// Shorthand: infer types, omit return, use $0/$1
let addShort: (Int, Int) -> Int = { $0 + $1 }
// ── Higher-order functions ───────────────────────────
let nums = [1, 2, 3, 4, 5]
// map — transform each element
let doubled = nums.map { $0 * 2 } // [2,4,6,8,10]
// filter — keep elements matching predicate
let evens = nums.filter { $0.isMultiple(of: 2) } // [2,4]
// reduce — fold into single value
let total = nums.reduce(0) { $0 + $1 } // 15
let total2 = nums.reduce(0, +) // same, operator shorthand
// sorted
let words = ["banana", "apple", "cherry"]
let sorted = words.sorted { $0 < $1 } // ["apple","banana","cherry"]
// compactMap — map + unwrap optionals
let raw = ["1", "two", "3"]
let ints = raw.compactMap { Int($0) } // [1, 3]
// Trailing closure syntax (when last argument is closure)
nums.forEach { print($0) }
// Capturing values
func makeCounter() -> () -> Int {
var count = 0
return { count += 1; return count }
}
let counter = makeCounter()
counter() // 1
counter() // 2
Collections
Arrays
var fruits: [String] = ["apple", "banana"]
fruits.append("cherry")
fruits.insert("avocado", at: 0)
fruits.remove(at: 1)
print(fruits.count) // count
print(fruits.isEmpty) // false
print(fruits.contains("apple")) // true
print(fruits.joined(separator: ", ")) // join to string
let sliced = fruits[0...1] // ArraySlice
Dictionaries
var scores: [String: Int] = ["Alice": 95, "Bob": 82]
scores["Carol"] = 91 // add/update
scores.removeValue(forKey: "Bob")
let aliceScore = scores["Alice"] // Optional Int!
for (name, score) in scores {
print("\(name): \(score)")
}
print(scores.keys) // keys collection
print(scores.values) // values collection
Sets
var tags: Set = ["swift", "ios", "apple"]
tags.insert("mobile")
tags.remove("apple")
print(tags.contains("ios")) // true
let a: Set = [1, 2, 3]
let b: Set = [2, 3, 4]
print(a.union(b)) // {1,2,3,4}
print(a.intersection(b)) // {2,3}
print(a.subtracting(b)) // {1}
Strings
Swift strings are Unicode-correct value types. They are not arrays of characters — every element is a Unicode grapheme cluster.
var s = "Hello, 🌍"
print(s.count) // 9 (not byte count)
print(s.isEmpty) // false
print(s.uppercased()) // HELLO, 🌍
print(s.hasPrefix("Hello")) // true
print(s.hasSuffix("🌍")) // true
print(s.contains(",")) // true
print(s.replacingOccurrences(of: "Hello", with: "Hi"))
// String interpolation
let n = 42
let msg = "Answer = \(n * 2)"
// Multi-line string literals
let poem = """
Roses are red,
Violets are blue,
Swift is fast,
And safe too.
"""
// Split and join
let csv = "a,b,c,d"
let parts = csv.split(separator: ",") // ["a","b","c","d"]
let back = parts.joined(separator: "-") // "a-b-c-d"
// String to Int and back
let maybeInt: Int? = Int("123") // Optional
let str = String(42) // "42"
Structs & Classes
Swift's key rule: Structs are value types (copied on assignment). Classes are reference types (shared). Prefer structs unless you need inheritance or reference semantics.
Struct (Value Type)
struct Rectangle {
var width: Double
var height: Double
// Computed property
var area: Double { width * height }
// Must mark mutating methods in structs
mutating func scale(by factor: Double) {
width *= factor
height *= factor
}
}
var r1 = Rectangle(width: 5, height: 3)
var r2 = r1 // COPIED — r2 is independent
r2.width = 10 // doesn't affect r1
print(r1.width) // still 5
Class (Reference Type)
class Animal {
var name: String
var age: Int
// Designated initializer
init(name: String, age: Int) {
self.name = name
self.age = age
}
// Convenience initializer
convenience init(name: String) {
self.init(name: name, age: 0)
}
func speak() -> String { "..." }
// Deinitializer (called when ref count hits 0)
deinit { print("\(name) freed") }
}
// Inheritance
class Dog: Animal {
var breed: String
init(name: String, breed: String) {
self.breed = breed
super.init(name: name, age: 0)
}
override func speak() -> String { "Woof!" }
}
let dog1 = Dog(name: "Rex", breed: "Labrador")
let dog2 = dog1 // REFERENCE — same object!
dog2.name = "Max"
print(dog1.name) // "Max" — both point to same instance
// Properties: stored, computed, property observers
class Thermostat {
var celsius: Double = 0 {
willSet { print("About to change to \(newValue)") }
didSet { print("Changed from \(oldValue)") }
}
var fahrenheit: Double {
get { celsius * 9 / 5 + 32 }
set { celsius = (newValue - 32) * 5 / 9 }
}
}
Enumerations
Swift enums are first-class types with methods, computed properties, and the ability to carry associated values — making them far more powerful than in C or Java.
// Basic enum
enum Direction {
case north, south, east, west
}
var heading = Direction.north
heading = .east // shorthand when type is known
// Raw values (like C enum)
enum Planet: Int {
case mercury = 1, venus, earth, mars
}
print(Planet.earth.rawValue) // 3
let p = Planet(rawValue: 2) // Optional<Planet>
// Associated values (powerful!)
enum Barcode {
case upc(Int, Int, Int, Int)
case qrCode(String)
}
let code: Barcode = .qrCode("ABCDEF")
switch code {
case .upc(let ns, let m, let p, let c):
print("UPC: \(ns)-\(m)-\(p)-\(c)")
case .qrCode(let str):
print("QR: \(str)")
}
// Enum with methods
enum Suit: String, CaseIterable {
case hearts, diamonds, clubs, spades
var isRed: Bool { self == .hearts || self == .diamonds }
}
// Iterate all cases (CaseIterable)
for suit in Suit.allCases {
print(suit.rawValue, suit.isRed)
}
Protocols
Protocols define a blueprint of requirements. Any type (struct, class, enum) can conform to a protocol. This is Swift's alternative to abstract classes and interfaces.
protocol Describable {
var description: String { get }
func describe()
}
// Default implementations via extensions
extension Describable {
func describe() { print(description) }
}
struct Car: Describable {
var make: String
var description: String { "Car: \(make)" }
}
let car = Car(make: "Tesla")
car.describe() // "Car: Tesla" (from extension)
// Protocol composition
protocol Named { var name: String { get } }
protocol Aged { var age: Int { get } }
func intro(entity: Named & Aged) {
print("\(entity.name) is \(entity.age)")
}
// Common built-in protocols
// Equatable — == and !=
// Hashable — can be used in Set/Dict keys
// Comparable — < > sorting
// Codable — JSON encode/decode
// CustomStringConvertible — print() hook
struct Point: Equatable, Hashable, CustomStringConvertible {
let x, y: Int
var description: String { "(\(x), \(y))" }
}
let set: Set<Point> = [Point(x: 1, y: 2), Point(x: 3, y: 4)]
Extensions
Extensions add new functionality to any type — even types you don't own like Int or String.
extension Int {
var isEven: Bool { self % 2 == 0 }
func times(_ action: () -> Void) {
for _ in 0..<self { action() }
}
}
print(7.isEven) // false
3.times { print("Hi") } // prints 3 times
Generics
Generics let you write flexible, reusable code that works with any type while maintaining type safety.
// Generic function
func swap<T>(_ a: inout T, _ b: inout T) {
let temp = a; a = b; b = temp
}
var x = 5, y = 10
swap(&x, &y) // works for any type T
// Generic Stack (type constraint)
struct Stack<Element> {
private var items: [Element] = []
mutating func push(_ item: Element) { items.append(item) }
mutating func pop() -> Element? { items.popLast() }
var top: Element? { items.last }
}
var stack = Stack<Int>()
stack.push(1); stack.push(2)
print(stack.pop()!) // 2
// Type constraints
func findMax<T: Comparable>(_ arr: [T]) -> T? {
arr.max()
}
findMax([3, 1, 7, 2]) // 7
findMax(["banana", "apple"]) // "banana"
Error Handling
Swift uses typed error handling with throw, try, and catch. Errors must conform to the Error protocol.
// 1. Define errors
enum ValidationError: Error {
case tooShort(minimum: Int)
case containsSpaces
case empty
}
// 2. Throwing function
func validateUsername(_ name: String) throws -> String {
guard !name.isEmpty else { throw ValidationError.empty }
guard name.count >= 3 else {
throw ValidationError.tooShort(minimum: 3)
}
guard !name.contains(" ") else {
throw ValidationError.containsSpaces
}
return name.lowercased()
}
// 3. Calling and catching
do {
let clean = try validateUsername("Alice")
print("Valid: \(clean)")
} catch ValidationError.tooShort(let min) {
print("Username must be >= \(min) chars")
} catch ValidationError.containsSpaces {
print("No spaces allowed")
} catch {
print("Error: \(error)") // catch-all
}
// try? converts to Optional (nil on error)
let result = try? validateUsername("hi") // nil
// try! force-unwraps (crashes on error)
let forced = try! validateUsername("alice123")
// defer — cleanup that always runs
func processFile() throws {
defer { print("File closed") } // runs no matter what
throw ValidationError.empty
}
Concurrency: async / await
Swift 5.5+ has first-class async/await for structured concurrency — cleaner than callbacks or Combine chains.
// Async function
func fetchUser(id: Int) async throws -> String {
// Simulate network delay
try await Task.sleep(nanoseconds: 1_000_000_000)
return "User \(id)"
}
// Calling async code
async {
do {
let user = try await fetchUser(id: 42)
print(user)
} catch {
print("Error: \(error)")
}
}
// Parallel tasks with async let
func fetchAll() async throws {
async let user1 = fetchUser(id: 1) // runs in parallel
async let user2 = fetchUser(id: 2)
let (u1, u2) = try await (user1, user2) // await both
print(u1, u2)
}
// Actors — thread-safe reference types
actor BankAccount {
var balance: Double = 0
func deposit(_ amount: Double) { balance += amount }
}
let acct = BankAccount()
await acct.deposit(100)
Grand Central Dispatch (GCD) still works in Swift, but async/await is preferred for new code. SwiftUI uses @MainActor to ensure UI updates happen on the main thread.
Beginner Project: Personal Finance Tracker
This single-file project covers structs, enums, protocols, generics, closures, error handling, collections, optionals, computed properties, and extensions — almost everything covered in this guide.
Save as finance.swift and run with swift finance.swift.
// ═══════════════════════════════════════════════════════════
// finance.swift — Personal Finance Tracker
// Covers: structs, enums, protocols, closures, error
// handling, generics, optionals, collections, etc.
// ═══════════════════════════════════════════════════════════
import Foundation
// ── 1. ENUMS: Transaction categories ─────────────────────
enum Category: String, CaseIterable {
case food = "🍔 Food"
case transport = "🚗 Transport"
case health = "💊 Health"
case income = "💵 Income"
case other = "📦 Other"
}
// ── 2. STRUCT: A single transaction ──────────────────────
struct Transaction: CustomStringConvertible {
let id: UUID // unique identifier
let date: Date
let amount: Double
let category: Category
let note: String
var isExpense: Bool { category != .income }
// Computed property: formatted amount string
var formattedAmount: String {
let sign = isExpense ? "-" : "+"
return "\(sign)$\(String(format: "%.2f", abs(amount)))"
}
// Protocol conformance: CustomStringConvertible
var description: String {
"[\(category.rawValue)] \(formattedAmount) — \(note)"
}
// Memberwise init with defaults
init(amount: Double, category: Category, note: String = "") {
self.id = UUID()
self.date = Date()
self.amount = amount
self.category = category
self.note = note
}
}
// ── 3. ERROR HANDLING: Budget errors ─────────────────────
enum BudgetError: Error {
case negativeBudget(Double)
case negativeAmount(Double)
case noTransactions
}
// ── 4. PROTOCOL: Something that can produce a summary ────
protocol Summarizable {
func summary() -> String
}
// ── 5. GENERIC HELPER: Find items matching predicate ─────
func find<T>(in items: [T], where predicate: (T) -> Bool) -> [T] {
items.filter(predicate)
}
// ── 6. STRUCT: Ledger (the main manager) ─────────────────
struct Ledger: Summarizable {
private var transactions: [Transaction] = []
var monthlyBudget: Double
init(budget: Double) throws {
guard budget >= 0 else {
throw BudgetError.negativeBudget(budget)
}
self.monthlyBudget = budget
}
// Add a transaction with validation
mutating func add(_ tx: Transaction) throws {
guard tx.amount > 0 else {
throw BudgetError.negativeAmount(tx.amount)
}
transactions.append(tx)
}
// Computed properties
var totalIncome: Double {
transactions
.filter { !$0.isExpense }
.reduce(0) { $0 + $1.amount }
}
var totalExpenses: Double {
transactions
.filter { $0.isExpense }
.reduce(0) { $0 + $1.amount }
}
var balance: Double { totalIncome - totalExpenses }
var budgetRemaining: Double { monthlyBudget - totalExpenses }
// Filter by category (uses generic helper)
func transactions(in category: Category) -> [Transaction] {
find(in: transactions) { $0.category == category }
}
// Most expensive category (uses map + sort)
func spendingByCategory() -> [(Category, Double)] {
Category.allCases
.map { cat -> (Category, Double) in
let total = transactions(in: cat)
.reduce(0) { $0 + $1.amount }
return (cat, total)
}
.filter { $0.1 > 0 }
.sorted { $0.1 > $1.1 }
}
// Largest single expense (optional return)
func largestExpense() throws -> Transaction {
let expenses = transactions.filter { $0.isExpense }
guard let max = expenses.max(by: { $0.amount < $1.amount }) else {
throw BudgetError.noTransactions
}
return max
}
// Protocol method: summary string
func summary() -> String {
let divider = String(repeating: "─", count: 40)
var lines = [String]()
lines.append("\n\(divider)")
lines.append(" 📒 FINANCE TRACKER SUMMARY")
lines.append(divider)
lines.append(" Budget: $\(String(format: "%.2f", monthlyBudget))")
lines.append(" Income: +$\(String(format: "%.2f", totalIncome))")
lines.append(" Expenses: -$\(String(format: "%.2f", totalExpenses))")
lines.append(" Balance: $\(String(format: "%.2f", balance))")
lines.append(" Remaining: $\(String(format: "%.2f", budgetRemaining))")
lines.append(divider)
lines.append(" Spending by category:")
for (cat, total) in spendingByCategory() {
lines.append(" \(cat.rawValue): $\(String(format: "%.2f", total))")
}
lines.append(divider)
return lines.joined(separator: "\n")
}
}
// ── 7. EXTENSION on Double: currency formatting ───────────
extension Double {
var asCurrency: String { "$\(String(format: "%.2f", self))" }
}
// ── 8. ENTRY POINT: main program ─────────────────────────
do {
// Initialise ledger (throwing init)
var ledger = try Ledger(budget: 2000.00)
// Add some transactions
let txData: [(Double, Category, String)] = [
(3200.00, .income, "Monthly salary"),
( 85.50, .food, "Grocery run"),
( 12.99, .food, "Coffee shop"),
( 45.00, .transport, "Gas"),
( 200.00, .health, "Gym membership"),
( 60.00, .food, "Restaurant dinner"),
( 29.99, .other, "Netflix"),
( 150.00, .transport, "Car repair"),
( 500.00, .income, "Freelance project"),
]
for (amt, cat, note) in txData {
let tx = Transaction(amount: amt, category: cat, note: note)
try ledger.add(tx)
}
// Print all food transactions using the generic find()
print("\n🍔 Food Transactions:")
ledger.transactions(in: .food).forEach { print(" \($0)") }
// Optionals in action: largest expense
let biggest = try ledger.largestExpense()
print("\n💸 Largest expense: \(biggest)")
// Closures: custom report using higher-order functions
let overFifty = ledger.transactions(in: .food)
.filter { $0.amount > 50 }
.map { $0.formattedAmount }
print("\n🔍 Food expenses over $50: \(overFifty)")
// Extension on Double in use
print("\n💰 Balance: \(ledger.balance.asCurrency)")
// Print summary (Summarizable protocol)
print(ledger.summary())
} catch BudgetError.negativeBudget(let val) {
print("❌ Invalid budget: \(val)")
} catch BudgetError.negativeAmount(let val) {
print("❌ Negative amount: \(val)")
} catch BudgetError.noTransactions {
print("❌ No transactions found")
} catch {
print("❌ Unexpected error: \(error)")
}
Expected Output
🍔 Food Transactions:
[🍔 Food] -$85.50 — Grocery run
[🍔 Food] -$12.99 — Coffee shop
[🍔 Food] -$60.00 — Restaurant dinner
💸 Largest expense: [💊 Health] -$200.00 — Gym membership
🔍 Food expenses over $50: ["-$85.50", "-$60.00"]
💰 Balance: $3116.53
────────────────────────────────────────
📒 FINANCE TRACKER SUMMARY
────────────────────────────────────────
Budget: $2000.00
Income: +$3700.00
Expenses: -$583.47
Balance: $3116.53
Remaining: $1416.53
────────────────────────────────────────
Spending by category:
💊 Health: $200.00
🚗 Transport: $195.00
🍔 Food: $158.49
📦 Other: $29.99
────────────────────────────────────────
What this project covers
| Feature | Where used |
|---|---|
| Structs (value types) | Transaction, Ledger |
| Enums with raw values & CaseIterable | Category, BudgetError |
| Protocols | Summarizable, CustomStringConvertible |
| Generics | find<T>() |
| Closures / HOF | .filter, .map, .reduce, .sorted |
| Error handling | throws, do/try/catch |
| Optionals & guard let | largestExpense() |
| Computed properties | totalIncome, balance, formattedAmount |
| Extensions | Double.asCurrency |
| Collections (Array, Dictionary) | Throughout Ledger |
| String interpolation + formatting | summary() |
| Tuples | txData array of tuples |
Where to Go From Here
SwiftUI
Apple's declarative UI framework. Build iOS/macOS apps using Swift state-driven views.
UIKit
The older, more verbose UI framework — still widely used and worth knowing.
Swift Package Manager
Build multi-file projects, import libraries, and structure real apps.
Vapor
Server-side Swift framework for building web APIs and backends.
Combine
Apple's reactive framework for handling asynchronous event streams.
Core ML / CreateML
Run machine learning models on-device with Swift's ML frameworks.
Recommended resources
| Resource | URL | Type |
|---|---|---|
| Swift.org docs | swift.org/documentation | Official |
| The Swift Programming Language (book) | docs.swift.org/swift-book | Official, free |
| Hacking with Swift | hackingwithswift.com | Tutorials |
| Swift by Sundell | swiftbysundell.com | Articles |
| Swift Playgrounds (iPad/Mac) | App Store | Interactive |
| 100 Days of SwiftUI | hackingwithswift.com/100swiftui | Course |
Next challenge: Extend the Finance Tracker above — add a Budget struct per category, a Report protocol with a csv() method, or hook it up to a SwiftUI view with @State and @ObservedObject.