Get 50% off during WWDC 2025!

Design+Code logo

Quick links

Suggested search

1. Introduction to Dictionaries

Dictionaries are one of Swift's most powerful collection types, designed for storing key-value pairs with fast lookups.

A. What is a Dictionary?

i. Definition: A dictionary is an unordered collection of key-value pairs, where each key is unique and maps to exactly one value.

ii. Purpose: Dictionaries provide a way to store data that is accessed via an identifier (key) rather than a position.

iii. Key characteristics: Keys must be unique and hashable, values can be of any type and can contain duplicates.

iv. Memory representation: Dictionaries use hash tables behind the scenes, making lookups extremely fast (O(1) complexity).

B. Why Use Dictionaries?

i. Natural mapping: When data has a natural key-value relationship (e.g., name → phone number).

ii. Fast lookups: Retrieve values quickly using their keys without iterating through the entire collection.

iii. Dynamic data association: Associate related data without predefined structures.

iv. Data transformation: Convert between different representations of data.

v. Caching and memoization: Store computed results for quick access.

2. Creating Dictionaries in Swift

A. Dictionary Literals

i. Using square brackets with key-value pairs: The most common way to create a dictionary.

let scores = ["Alice": 95, "Bob": 82, "Charlie": 90]

// Empty dictionary literal
let emptyDictionary: [String: Int] = [:]

ii. Empty dictionaries: Creating dictionaries with no elements.

// Method 1: Type annotation with empty dictionary literal
let emptyScores: [String: Int] = [:]

// Method 2: Using initializer syntax
let anotherEmptyDict = [String: Double]()

// Method 3: Using Dictionary type explicitly
let yetAnotherEmpty = Dictionary<String, Int>()

B. Dictionary Type Annotation

i. Explicit type declaration: Declaring the types of keys and values a dictionary will contain.

let heights: [String: Double] = ["Tim": 1.78, "Emma": 1.65, "John": 1.83]

// With generic syntax
let weights: Dictionary<String, Double> = ["Tim": 75.0, "Emma": 62.5, "John": 80.2]

ii. Dictionary conversion: Creating dictionaries from sequences of key-value pairs.

let pairs = [("Alice", 95), ("Bob", 82), ("Charlie", 90)]
let studentScores = Dictionary(uniqueKeysWithValues: pairs)
// Result: ["Alice": 95, "Bob": 82, "Charlie": 90]

// Handling potentially duplicate keys by keeping the first occurrence
let duplicatePairs = [("Alice", 95), ("Bob", 82), ("Alice", 100)]
let scoresKeepingFirst = Dictionary(duplicatePairs, uniquingKeysWith: { first, _ in first })
// Result: ["Alice": 95, "Bob": 82]

// Handling potentially duplicate keys by keeping the last occurrence
let scoresKeepingLast = Dictionary(duplicatePairs, uniquingKeysWith: { _, last in last })
// Result: ["Alice": 100, "Bob": 82]

iii. Dictionaries with custom types: Using your own types as keys and values.

struct User: Hashable {
    let id: Int
    let name: String
}

struct Profile {
    let bio: String
    let isVerified: Bool
}

let userProfiles: [User: Profile] = [
    User(id: 1, name: "Alice"): Profile(bio: "iOS Developer", isVerified: true),
    User(id: 2, name: "Bob"): Profile(bio: "UX Designer", isVerified: false)
]

iv. Creating from arrays: Transforming arrays into dictionaries.

let names = ["Alice", "Bob", "Charlie"]
let nameIndices = Dictionary(uniqueKeysWithValues: zip(names, 0...))
// Result: ["Alice": 0, "Bob": 1, "Charlie": 2]

// Creating a dictionary by transforming an array
let students = ["Alice", "Bob", "Charlie"]
let studentGrades = Dictionary(uniqueKeysWithValues: students.map { ($0, 0) })
// Result: ["Alice": 0, "Bob": 0, "Charlie": 0] (initializing with zeros)

// Group values by a computed key
let words = ["Apple", "Banana", "Apricot", "Cherry", "Blueberry"]
let groupedByFirstLetter = Dictionary(grouping: words) { $0.first! }
// Result: ["A": ["Apple", "Apricot"], "B": ["Banana", "Blueberry"], "C": ["Cherry"]]

3. Accessing Dictionary Elements

A. Using Keys

i. Subscript notation: Access values using their keys.

let scores = ["Alice": 95, "Bob": 82, "Charlie": 90]
let aliceScore = scores["Alice"] // Returns Optional(95)
let davidScore = scores["David"] // Returns nil (not in dictionary)

// Note that dictionary subscript returns an optional value
if let bobScore = scores["Bob"] {
    print("Bob's score is \(bobScore)")
} else {
    print("No score for Bob")
}

ii. Safe access with default values: Provide fallbacks for missing keys.

// Get a value or use a default if the key doesn't exist
let eveScore = scores["Eve", default: 0] // Returns 0 instead of nil

// Dictionary with default values
let defaultScores = scores.withDefaultValue(0)
let frankScore = defaultScores["Frank"] // Returns 0

iii. Key existence check: Check if a key exists in the dictionary.

let hasAlice = scores.keys.contains("Alice") // true
let hasEve = scores.keys.contains("Eve") // false

// More concise approach (but performs the lookup twice)
if scores["David"] != nil {
    print("David has a score")
}

B. Properties and Methods for Access

i. keys and values properties: Access all keys or all values as collections.

let scores = ["Alice": 95, "Bob": 82, "Charlie": 90]

// Get all keys
let students = scores.keys // Dictionary.Keys collection
let studentArray = Array(scores.keys) // Convert to array: ["Alice", "Bob", "Charlie"]

// Get all values
let allScores = scores.values // Dictionary.Values collection
let scoreArray = Array(scores.values) // Convert to array: [95, 82, 90]

// Note: Order is not guaranteed

**ii. randomElement(): Get a random key-value pair.

if let (student, score) = scores.randomElement() {
    print("Random student: \(student) with score: \(score)")
}

iii. Finding elements by value: Search for keys based on their values.

// Find the key for a value (just the first occurrence if multiple exist)
let highScorer = scores.first { $0.value == scores.values.max() }?.key
print("Highest scorer is \(highScorer ?? "unknown")") // "Alice"

// Find all keys for a specific value
let studentsWithScore90 = scores.filter { $0.value == 90 }.map { $0.key }
// Result: ["Charlie"]

4. Modifying Dictionaries

A. Adding Elements

i. Subscript assignment: Add key-value pairs using subscript notation.

var scores = ["Alice": 95, "Bob": 82]
scores["Charlie"] = 90 // Add new key-value pair
// scores is now ["Alice": 95, "Bob": 82, "Charlie": 90]

// Update existing value
scores["Bob"] = 85
// scores is now ["Alice": 95, "Bob": 85, "Charlie": 90]

**ii. updateValue(_:forKey:): Add or update a value, returning the old value if it existed.

let oldValue = scores.updateValue(88, forKey: "Alice")
// oldValue is Optional(95)
// scores is now ["Alice": 88, "Bob": 85, "Charlie": 90]

let previousDavid = scores.updateValue(78, forKey: "David")
// previousDavid is nil (because "David" wasn't in the dictionary)
// scores is now ["Alice": 88, "Bob": 85, "Charlie": 90, "David": 78]

iii. Merging dictionaries: Combine two dictionaries.

var mainScores = ["Alice": 95, "Bob": 82, "Charlie": 90]
let additionalScores = ["David": 88, "Eve": 94, "Alice": 100]

// Merge, keeping existing values for duplicate keys
mainScores.merge(additionalScores) { current, _ in current }
// mainScores is now ["Alice": 95, "Bob": 82, "Charlie": 90, "David": 88, "Eve": 94]

// Merge, using new values for duplicate keys
mainScores = ["Alice": 95, "Bob": 82, "Charlie": 90]  // Reset
mainScores.merge(additionalScores) { _, new in new }
// mainScores is now ["Alice": 100, "Bob": 82, "Charlie": 90, "David": 88, "Eve": 94]

// Create a new merged dictionary without modifying originals
let combinedScores = mainScores.merging(additionalScores) { current, _ in current }

B. Removing Elements

i. Set to nil: Remove a key-value pair by setting its value to nil.

var scores = ["Alice": 95, "Bob": 82, "Charlie": 90, "David": 88]
scores["Bob"] = nil
// scores is now ["Alice": 95, "Charlie": 90, "David": 88]

// Setting a non-existent key to nil has no effect
scores["Eve"] = nil
// scores remains unchanged

**ii. removeValue(forKey:): Remove a key-value pair and return the value.

let removedValue = scores.removeValue(forKey: "Charlie")
// removedValue is Optional(90)
// scores is now ["Alice": 95, "David": 88]

let nonExistentRemoval = scores.removeValue(forKey: "Frank")
// nonExistentRemoval is nil
// scores remains unchanged

**iii. removeAll(): Clear the entire dictionary.

scores.removeAll()
// scores is now an empty dictionary: [:]

// You can keep capacity allocated for performance reasons:
scores.removeAll(keepingCapacity: true)

iv. Filtering keys: Remove multiple entries based on a condition.

var studentScores = ["Alice": 95, "Bob": 65, "Charlie": 90, "David": 72]

// Remove all students with scores below 70
studentScores = studentScores.filter { $0.value >= 70 }
// studentScores is now ["Alice": 95, "Charlie": 90, "David": 72]

// Alternative approach that modifies in place
for (name, score) in studentScores {
    if score < 80 {
        studentScores.removeValue(forKey: name)
    }
}

C. Updating Elements

i. Direct assignment: Update existing values.

var scores = ["Alice": 95, "Bob": 82, "Charlie": 90]
scores["Bob"] = 85
// scores is now ["Alice": 95, "Bob": 85, "Charlie": 90]

ii. Conditional updates: Update values only if they meet certain criteria.

for (name, score) in scores {
    if score < 90 {
        scores[name] = score + 5 // Add 5 bonus points to scores below 90
    }
}
// scores might now be ["Alice": 95, "Bob": 90, "Charlie": 90]

iii. Transforming values: Update all values using a transform function.

// Add 3 points to everyone's score
scores = scores.mapValues { $0 + 3 }
// scores is now ["Alice": 98, "Bob": 93, "Charlie": 93]

// Apply a different transformation based on the key
for name in scores.keys {
    if name.starts(with: "A") {
        scores[name] = scores[name, default: 0] + 2 // Extra points for A names
    }
}
// scores might now be ["Alice": 100, "Bob": 93, "Charlie": 93]

5. Dictionary Operations and Methods

A. Transforming Dictionaries

**i. mapValues(_:): Transform every value while keeping the same keys.

let scores = ["Alice": 95, "Bob": 82, "Charlie": 90]

// Convert to letter grades
let letterGrades = scores.mapValues { score -> String in
    switch score {
    case 90...100: return "A"
    case 80..<90: return "B"
    case 70..<80: return "C"
    case 60..<70: return "D"
    default: return "F"
    }
}
// letterGrades is ["Alice": "A", "Bob": "B", "Charlie": "A"]

**ii. compactMapValues(_:): Transform values and remove nil results.

let possibleNumbers = ["one": "1", "two": "2", "three": "three", "four": "4"]

// Convert string values to integers, removing entries that can't be converted
let validNumbers = possibleNumbers.compactMapValues { Int($0) }
// validNumbers is ["one": 1, "two": 2, "four": 4]

iii. Creating a new dictionary with transformed keys and values: Custom mapping functions.

let scores = ["Alice": 95, "Bob": 82, "Charlie": 90]

// Create a new dictionary with lowercase keys and values as strings
let formattedScores = scores.reduce(into: [String: String]()) { result, entry in
    result[entry.key.lowercased()] = "\(entry.value)%"
}
// formattedScores is ["alice": "95%", "bob": "82%", "charlie": "90%"]

B. Filtering Dictionaries

**i. filter(_:): Keep only key-value pairs that satisfy a condition.

let scores = ["Alice": 95, "Bob": 82, "Charlie": 90, "David": 65]

// Keep only scores of 80 or above
let highScores = scores.filter { $0.value >= 80 }
// highScores is ["Alice": 95, "Bob": 82, "Charlie": 90]

// Keep only scores where the name starts with a certain letter
let aStudents = scores.filter { $0.key.starts(with: "A") }
// aStudents is ["Alice": 95]

ii. Advanced filtering: Combine multiple conditions.

// Students with names longer than 4 characters and scores above 85
let filteredScores = scores.filter { $0.key.count > 4 && $0.value > 85 }
// filteredScores might be ["Alice": 95, "Charlie": 90]

// Filter based on both key and value relationships
let specialCases = scores.filter { name, score in
    name.lowercased().first == "a" || score > 90
}
// specialCases might be ["Alice": 95]

C. Combining Dictionaries

**i. merge(_:uniquingKeysWith:): Combine two dictionaries in place.

var mainScores = ["Alice": 95, "Bob": 82]
let additionalScores = ["Charlie": 90, "Bob": 85]

// Merge, keeping existing values for duplicate keys
mainScores.merge(additionalScores) { current, _ in current }
// mainScores is now ["Alice": 95, "Bob": 82, "Charlie": 90]

// Merge, using new values for duplicate keys
mainScores = ["Alice": 95, "Bob": 82]  // Reset
mainScores.merge(additionalScores) { _, new in new }
// mainScores is now ["Alice": 95, "Bob": 85, "Charlie": 90]

// Custom merge logic
mainScores = ["Alice": 95, "Bob": 82]  // Reset
mainScores.merge(additionalScores) { current, new in max(current, new) }
// mainScores is now ["Alice": 95, "Bob": 85, "Charlie": 90]

**ii. merging(_:uniquingKeysWith:): Create a new dictionary from the combination of two dictionaries.

let firstHalf = ["A": 1, "B": 2, "C": 3]
let secondHalf = ["C": 30, "D": 4, "E": 5]

// Create new dictionary without modifying originals
let combined = firstHalf.merging(secondHalf) { first, second in first + second }
// combined is ["A": 1, "B": 2, "C": 33, "D": 4, "E": 5]

iii. Manual combination: Implement custom dictionary merging logic.

let averageScores = ["Alice": [85, 90, 95], "Bob": [82, 85, 88]]
let finalExams = ["Alice": 92, "Bob": 88, "Charlie": 95]

// Custom combination: add final exam to list of scores
var updatedScores = averageScores
for (student, finalScore) in finalExams {
    if var scores = updatedScores[student] {
        scores.append(finalScore)
        updatedScores[student] = scores
    } else {
        updatedScores[student] = [finalScore]
    }
}
// updatedScores might be ["Alice": [85, 90, 95, 92], "Bob": [82, 85, 88, 88], "Charlie": [95]]

6. Dictionary Iteration

A. Iterating through Key-Value Pairs

i. Using for-in loops: Iterate through all key-value pairs.

let scores = ["Alice": 95, "Bob": 82, "Charlie": 90]

for (name, score) in scores {
    print("\(name): \(score)")
}
// Output (order not guaranteed):
// Alice: 95
// Bob: 82
// Charlie: 90

ii. Iterating with keys and values separately: Access all keys or all values.

// Iterating through all keys
for name in scores.keys {
    print("Student: \(name)")
}

// Iterating through all values
for score in scores.values {
    print("Score: \(score)")
}

// Using keys to access values
for name in scores.keys.sorted() { // Sort keys for consistent order
    print("\(name): \(scores[name]!)")
}
// Output:
// Alice: 95
// Bob: 82
// Charlie: 90

B. Functional Iteration

**i. Using forEach: Apply an operation to each key-value pair.

scores.forEach { name, score in
    print("\(name) scored \(score) points")
}

// Separate forEach on keys and values
scores.keys.forEach { print("Student: \($0)") }
scores.values.forEach { print("Score: \($0)") }

ii. Combining with other operations: Chain functional methods for concise code.

// Print only students with high scores
scores.filter { $0.value >= 90 }
       .forEach { print("\($0.key) is a top scorer with \($0.value) points") }

// Find the highest score and the student who achieved it
if let topStudent = scores.max(by: { $0.value < $1.value }) {
    print("Top student is \(topStudent.key) with \(topStudent.value) points")
}

// Calculate the average score
let average = scores.values.reduce(0, +) / scores.count
print("Class average: \(average)")

iii. Sorted iteration: Iterate through key-value pairs in a specific order.

// Sort by name (key) alphabetically
for (name, score) in scores.sorted(by: { $0.key < $1.key }) {
    print("\(name): \(score)")
}

// Sort by score (value) from highest to lowest
for (name, score) in scores.sorted(by: { $0.value > $1.value }) {
    print("\(name): \(score)")
}

7. Common Dictionary Patterns

A. Counting Occurrences

i. Building frequency maps: Count occurrences of elements.

let words = ["apple", "banana", "apple", "orange", "banana", "apple"]

// Count occurrences of each word
var wordCounts = [String: Int]()
for word in words {
    wordCounts[word, default: 0] += 1
}
// wordCounts is ["apple": 3, "banana": 2, "orange": 1]

// Shorter functional approach
let functionalCounts = words.reduce(into: [String: Int]()) { counts, word in
    counts[word, default: 0] += 1
}

ii. Character frequency analysis: Count characters in a string.

let message = "hello world"
let characterCounts = message.reduce(into: [Character: Int]()) { counts, char in
    counts[char, default: 0] += 1
}
// characterCounts is ["h": 1, "e": 1, "l": 3, "o": 2, " ": 1, "w": 1, "r": 1, "d": 1]

B. Grouping and Categorizing Data

i. Grouping items by a property: Create dictionaries of categorized data.

struct Student {
    let name: String
    let grade: String
    let score: Int
}

let students = [
    Student(name: "Alice", grade: "A", score: 95),
    Student(name: "Bob", grade: "B", score: 82),
    Student(name: "Charlie", grade: "A", score: 90),
    Student(name: "David", grade: "C", score: 75)
]

// Group students by grade
let studentsByGrade = Dictionary(grouping: students, by: { $0.grade })
// studentsByGrade is:
// ["A": [Student(name: "Alice"...), Student(name: "Charlie"...)],
//  "B": [Student(name: "Bob"...)],
//  "C": [Student(name: "David"...)]]

// Get all students with grade A
let aStudents = studentsByGrade["A"] ?? []

ii. Building lookup tables: Create dictionaries for fast data retrieval.

// Build a lookup table by ID
let studentsById = students.reduce(into: [String: Student]()) { dict, student in
    dict[student.name] = student
}

// Fast lookup by name
if let alice = studentsById["Alice"] {
    print("Found: \(alice.name) with score: \(alice.score)")
}

C. Caching and Memoization

i. Using dictionaries to cache expensive calculations: Improve performance through memoization.

// A function that caches its results
func fibonacci(_ n: Int, cache: inout [Int: Int]) -> Int {
    // Return cached result if available
    if let cached = cache[n] {
        return cached
    }

    // Base cases
    if n <= 1 {
        cache[n] = n
        return n
    }

    // Calculate, cache, and return
    let result = fibonacci(n - 1, cache: &cache) + fibonacci(n - 2, cache: &cache)
    cache[n] = result
    return result
}

var fibCache = [Int: Int]()
let fib10 = fibonacci(10, cache: &fibCache)
print("Fibonacci(10) = \(fib10)") // 55

// The cache now contains all fibonacci values from 0 to 10
print("Cache size: \(fibCache.count)") // 11

ii. Implementing a simple LRU (Least Recently Used) cache: Limit cache size.

class LRUCache<Key: Hashable, Value> {
    private let capacity: Int
    private var cache = [Key: Value]()
    private var accessOrder = [Key]()

    init(capacity: Int) {
        self.capacity = capacity
    }

    func get(_ key: Key) -> Value? {
        guard let value = cache[key] else { return nil }

        // Update access order
        if let index = accessOrder.firstIndex(of: key) {
            accessOrder.remove(at: index)
        }
        accessOrder.append(key)

        return value
    }

    func put(_ key: Key, _ value: Value) {
        // Update access order
        if let index = accessOrder.firstIndex(of: key) {
            accessOrder.remove(at: index)
        }

        // Check if we need to evict the least recently used item
        if cache.count >= capacity && !accessOrder.isEmpty && cache[key] == nil {
            let leastUsed = accessOrder.removeFirst()
            cache.removeValue(forKey: leastUsed)
        }

        // Add the new item
        cache[key] = value
        accessOrder.append(key)
    }
}

// Usage
let cache = LRUCache<String, Int>(capacity: 2)
cache.put("A", 1)
cache.put("B", 2)
print(cache.get("A")) // Optional(1)
cache.put("C", 3) // Evicts "B" since "A" was used more recently
print(cache.get("B")) // nil
print(cache.get("C")) // Optional(3)

8. Dictionaries in SwiftUI

A. Using Dictionaries for View State

i. Managing complex state: Track multiple pieces of related state in a view.

struct FormView: View {
    @State private var formValues: [String: String] = [
        "name": "",
        "email": "",
        "phone": ""
    ]
    @State private var validationErrors: [String: String] = [:]

    var body: some View {
        Form {
            TextField("Name", text: Binding(
                get: { formValues["name"] ?? "" },
                set: { formValues["name"] = $0 }
            ))
            if let error = validationErrors["name"] {
                Text(error).foregroundColor(.red)
            }

            TextField("Email", text: Binding(
                get: { formValues["email"] ?? "" },
                set: { formValues["email"] = $0 }
            ))
            if let error = validationErrors["email"] {
                Text(error).foregroundColor(.red)
            }

            Button("Submit") {
                validateForm()
            }
        }
    }

    func validateForm() {
        validationErrors = [:]

        if formValues["name"]?.isEmpty ?? true {
            validationErrors["name"] = "Name is required"
        }

        if let email = formValues["email"], !email.contains("@") {
            validationErrors["email"] = "Invalid email format"
        }
    }
}

ii. Building preference systems: Store and manage user preferences.

struct UserPreferences: View {
    @AppStorage("userPreferences") var preferencesData: Data = Data()
    @State private var preferences: [String: Bool] = [
        "darkMode": false,
        "notifications": true,
        "soundEffects": true
    ]

    var body: some View {
        List {
            Toggle("Dark Mode", isOn: bindingFor("darkMode"))
            Toggle("Notifications", isOn: bindingFor("notifications"))
            Toggle("Sound Effects", isOn: bindingFor("soundEffects"))
        }
        .onAppear {
            loadPreferences()
        }
    }

    func bindingFor(_ key: String) -> Binding<Bool> {
        Binding(
            get: { preferences[key] ?? false },
            set: { newValue in
                preferences[key] = newValue
                savePreferences()
            }
        )
    }

    func loadPreferences() {
        if let decodedPreferences = try? JSONDecoder().decode([String: Bool].self, from: preferencesData) {
            preferences = decodedPreferences
        }
    }

    func savePreferences() {
        if let encodedData = try? JSONEncoder().encode(preferences) {
            preferencesData = encodedData
        }
    }
}

B. Data Transformation for Views

i. Converting data models for display: Map backend data to view-friendly formats.

struct ProductsView: View {
    // Raw data from backend
    let productsData: [String: [String: Any]] = [
        "p1": ["name": "iPhone", "price": 999, "stock": 10],
        "p2": ["name": "iPad", "price": 799, "stock": 5],
        "p3": ["name": "MacBook", "price": 1299, "stock": 8]
    ]

    // Transformed data for the view
    var displayProducts: [String: DisplayProduct] {
        productsData.compactMapValues { data -> DisplayProduct? in
            guard let name = data["name"] as? String,
                  let price = data["price"] as? Int,
                  let stock = data["stock"] as? Int else {
                return nil
            }

            return DisplayProduct(
                name: name,
                formattedPrice: "$\(price)",
                inStock: stock > 0,
                stockDisplay: stock > 0 ? "\(stock) in stock" : "Out of stock"
            )
        }
    }

    var body: some View {
        List {
            ForEach(Array(displayProducts.keys), id: \.self) { productId in
                if let product = displayProducts[productId] {
                    HStack {
                        VStack(alignment: .leading) {
                            Text(product.name).font(.headline)
                            Text(product.formattedPrice)
                        }
                        Spacer()
                        Text(product.stockDisplay)
                            .foregroundColor(product.inStock ? .green : .red)
                    }
                }
            }
        }
    }

    struct DisplayProduct {
        let name: String
        let formattedPrice: String
        let inStock: Bool
        let stockDisplay: String
    }
}

ii. Building dynamic navigation systems: Use dictionaries to manage navigation destinations.

struct TabNavigator: View {
    enum Tab {
        case home, search, profile
    }

    @State private var selectedTab: Tab = .home

    // Dictionary of views for each tab
    private let tabViews: [Tab: AnyView] = [
        .home: AnyView(Text("Home Screen").font(.largeTitle)),
        .search: AnyView(Text("Search Screen").font(.largeTitle)),
        .profile: AnyView(Text("Profile Screen").font(.largeTitle))
    ]

    // Dictionary of icon names for each tab
    private let tabIcons: [Tab: String] = [
        .home: "house",
        .search: "magnifyingglass",
        .profile: "person"
    ]

    var body: some View {
        VStack {
            // Current view based on selected tab
            tabViews[selectedTab]

            Spacer()

            // Tab bar
            HStack {
                ForEach([Tab.home, .search, .profile], id: \.self) { tab in
                    Spacer()
                    Button(action: {
                        selectedTab = tab
                    }) {
                        VStack {
                            Image(systemName: tabIcons[tab] ?? "")
                                .font(.system(size: 24))
                            Text(String(describing: tab).capitalized)
                                .font(.caption)
                        }
                    }
                    .foregroundColor(selectedTab == tab ? .blue : .gray)
                    Spacer()
                }
            }
            .padding(.top)
            .background(Color(.systemBackground))
        }
    }
}

iii. Feature flags and configuration: Use dictionaries to control feature availability.

struct FeatureFlagManager {
    // Dictionary of feature flags
    static var features: [String: Bool] = [
        "newCheckout": true,
        "darkModeSupport": true,
        "voiceSearch": false,
        "biometricLogin": true
    ]

    // Dictionary of feature configurations
    static var config: [String: Any] = [
        "maxItems": 10,
        "refreshInterval": 60.0,
        "welcomeMessage": "Welcome to the app!",
        "apiEndpoint": "https://api.example.com/v2"
    ]

    static func isEnabled(_ feature: String) -> Bool {
        return features[feature] ?? false
    }

    static func config<T>(for key: String, defaultValue: T) -> T {
        return config[key] as? T ?? defaultValue
    }
}

struct FeatureFlaggedView: View {
    var body: some View {
        VStack {
            Text(FeatureFlagManager.config(for: "welcomeMessage", defaultValue: "Hello!"))
                .font(.headline)

            if FeatureFlagManager.isEnabled("newCheckout") {
                Button("Checkout v2") {
                    // New checkout flow
                }
                .padding()
                .background(Color.blue)
                .foregroundColor(.white)
                .cornerRadius(8)
            } else {
                Button("Checkout") {
                    // Original checkout flow
                }
                .padding()
                .background(Color.gray)
                .foregroundColor(.white)
                .cornerRadius(8)
            }

            if FeatureFlagManager.isEnabled("voiceSearch") {
                Button(action: {
                    // Voice search functionality
                }) {
                    Image(systemName: "mic.fill")
                        .font(.largeTitle)
                }
            }
        }
        .padding()
    }
}

9. Best Practices and Tips

A. Safety Considerations

i. Handling nil values: Since dictionary subscripting returns optionals, handle them safely.

let scores = ["Alice": 95, "Bob": 82, "Charlie": 90]

// Unsafe - could crash if key doesn't exist
// let bobScore = scores["Bob"]!

// Safe options:
// Option 1: Optional binding
if let bobScore = scores["Bob"] {
    print("Bob's score is \(bobScore)")
} else {
    print("No score for Bob")
}

// Option 2: Default value using nil coalescing
let davidScore = scores["David"] ?? 0
print("David's score is \(davidScore)") // "David's score is 0"

// Option 3: Default value using subscript syntax
let eveScore = scores["Eve", default: 0]
print("Eve's score is \(eveScore)") // "Eve's score is 0"

ii. Consistent key handling: Pay attention to key case and formatting.

var userSettings = [String: Any]()

// Inconsistent keys can lead to bugs
userSettings["userName"] = "john_doe"
userSettings["UserName"] = "jane_doe" // Different key!

// Approach 1: Define keys as constants
enum SettingKey {
    static let userName = "userName"
    static let theme = "theme"
    static let fontSize = "fontSize"
}

userSettings[SettingKey.userName] = "consistent_access"

// Approach 2: Use an enum with string raw values
enum Setting: String {
    case userName
    case theme
    case fontSize
}

userSettings[Setting.userName.rawValue] = "also_consistent"

iii. Type safety with dictionaries: Ensure type consistency for values.

// Mixed value types can cause problems
let mixedDict: [String: Any] = [
    "name": "John",
    "age": 30,
    "isActive": true
]

// Need type casting for each value
if let name = mixedDict["name"] as? String,
   let age = mixedDict["age"] as? Int {
    print("\(name) is \(age) years old")
}

// Better approach: Use separate dictionaries or structs for different types
let stringValues: [String: String] = ["name": "John", "email": "john@example.com"]
let intValues: [String: Int] = ["age": 30, "score": 95]

// Or better yet, use a struct
struct User {
    let name: String
    let age: Int
    let isActive: Bool
}

let user = User(name: "John", age: 30, isActive: true)

iv. Default values for non-existent keys: Use the default parameter of subscripts.

let scores = ["Alice": 95, "Bob": 82]

// Before Swift 4
let charlieScore = scores["Charlie"] ?? 0

// In Swift 4 and later, more concise:
let davidScore = scores["David", default: 0]

// Useful in counting scenarios
var wordCounts = [String: Int]()
let words = ["apple", "banana", "apple", "cherry"]

for word in words {
    // Increment without separate check for existence
    wordCounts[word, default: 0] += 1
}
// wordCounts is ["apple": 2, "banana": 1, "cherry": 1]

B. Performance Tips

i. Capacity management: Pre-allocate for better performance with large dictionaries.

// When you know the approximate size in advance
var largeDict = [String: Int]()
largeDict.reserveCapacity(10000)

// Populate the dictionary
for i in 1...10000 {
    largeDict["key\(i)"] = i
}

ii. Key choice considerations: Choose appropriate keys for dictionaries.

// Strings as keys (common and convenient)
var stringKeyDict = ["name": "John", "email": "john@example.com"]

// Integers as keys (faster for lookup)
var intKeyDict = [1: "One", 2: "Two", 3: "Three"]

// Enums as keys (type-safe and readable)
enum UserAttribute: Hashable {
    case name
    case email
    case age
}

var enumKeyDict = [
    UserAttribute.name: "John",
    UserAttribute.email: "john@example.com",
    UserAttribute.age: 30
]

// Structs as complex keys (must conform to Hashable)
struct UserKey: Hashable {
    let id: Int
    let domain: String
}

var structKeyDict = [
    UserKey(id: 1, domain: "example.com"): "John",
    UserKey(id: 2, domain: "example.com"): "Jane"
]

iii. Hashable conformance efficiency: Implement hashable correctly for custom types.

struct User: Hashable {
    let id: Int
    let name: String
    let email: String

    // Optimize hashing by only using fields that make the object unique
    func hash(into hasher: inout Hasher) {
        // Just using ID if it's guaranteed unique is more efficient
        hasher.combine(id)

        // Don't do this unless necessary for your use case:
        // hasher.combine(name)
        // hasher.combine(email)
    }

    // Optimized equality operator to match hash function
    static func == (lhs: User, rhs: User) -> Bool {
        return lhs.id == rhs.id
    }
}

// Dictionary with User keys
var userSettings = [User: [String: Any]]()

iv. Avoiding excessive copying: Be aware of dictionary copy-on-write behavior.

// Dictionaries use copy-on-write semantics
var originalDict = ["key1": "value1", "key2": "value2"]
var reference = originalDict // No copy is made yet

// Modification triggers a copy
reference["key3"] = "value3"
// Now originalDict is still ["key1": "value1", "key2": "value2"]
// And reference is ["key1": "value1", "key2": "value2", "key3": "value3"]

// For performance-critical code with large dictionaries
func processDictionary(_ dict: [String: String]) {
    // This doesn't copy the dictionary because it's not modified
    for (key, value) in dict {
        print("\(key): \(value)")
    }
}

func modifyDictionary(_ dict: inout [String: String]) {
    // Using inout avoids unnecessary copies for modification
    dict["newKey"] = "newValue"
}

// Function call with large dictionary
modifyDictionary(&originalDict)

v. Choosing between dictionary and other data structures: Use the right tool for the job.

// Dictionary: Best for key-based lookup
let scoresByName = ["Alice": 95, "Bob": 82, "Charlie": 90]
let aliceScore = scoresByName["Alice"] // O(1) lookup

// Array: Best for ordered data that needs index access
let orderedScores = [95, 82, 90]
let firstScore = orderedScores[0] // O(1) lookup by index

// Set: Best for unique membership testing without needing associated values
let uniqueNames: Set = ["Alice", "Bob", "Charlie"]
let hasBob = uniqueNames.contains("Bob") // O(1) lookup

// For frequency counting, dictionary is best
let letters = "mississippi"
var letterCounts = [Character: Int]()
for char in letters {
    letterCounts[char, default: 0] += 1
}
// letterCounts is ["m": 1, "i": 4, "s": 4, "p": 2]

Conclusion

Dictionaries are a powerful and flexible collection type in Swift, providing an efficient way to store and retrieve values based on keys. They excel in scenarios where you need quick lookups, natural key-value mappings, and the ability to dynamically associate data.

Unlike arrays, dictionaries don't maintain an ordered sequence of elements but instead organize data through unique keys, making them perfect for many real-world data representations like user profiles, configuration settings, lookup tables, and caches.

By mastering dictionaries, you'll add an essential tool to your Swift programming toolkit that will help you write more efficient, expressive, and maintainable code. Whether you're building complex data structures, managing application state, or transforming data for presentation, dictionaries are often the ideal collection type for the job.

Learn with videos and source files. Available to Pro subscribers only.

Purchase includes access to 50+ courses, 320+ premium tutorials, 300+ hours of videos, source files and certificates.

BACK TO

Swift Sets

READ NEXT

For-Loops and Range

Templates and source code

Download source files

Download the videos and assets to refer and learn offline without interuption.

check

Design template

check

Source code for all sections

check

Video files, ePub and subtitles

Videos

Assets

1

Building Your iOS Development Foundation

Master the fundamentals of Swift programming with hands-on examples designed for beginners and experienced developers alike

2

SwiftUI Print Debugging

Print debugging: Unlock the invisible processes with strategic print statements that illuminate state changes, view lifecycles and data flow

18:04

3

Comments Documentation Waypoints

Transform your code from mysterious instructions to a comprehensive narrative with strategic comments that explain the why

22:02

4

Variables and Constants

Learn when and how to use variables and constants to write safer, more efficient SwiftUI code

11:37

5

Strings and Interpolation

Learn essential string operations in Swift: Build better iOS apps with efficient text handling techniques

13:22

6

Swift Operators: The Foundation of SwiftUI Logic

Building powerful iOS apps through the language of operations

5:34

7

Swift Unary Operators

Mastering the elegant simplicity of unary operators for cleaner, more expressive SwiftUI code that transforms your UI with minimal syntax

15:00

8

Swift Binary Operators

Master the two-operand symbols that transform complex interface logic into concise, readable declarations

3:36

9

Arithmetic Operators

Learn how to implement and optimize arithmetic operations in SwiftUI, from basic calculations to complex mathematical interfaces

6:11

10

If-Else and Comparison Operators

Building Dynamic SwiftUI: Mastering If-Else and Comparison Operators

12:32

11

Logical Operators

Master SwiftUI's logical operators: Building intelligent iOS apps with robust decision-making systems

6:03

12

Ternary Operators

Use the power of Swift's ternary conditional operator to create dynamic, responsive interfaces with minimal code in SwiftUI

6:22

13

Blocks and Scope

A comprehensive guide to writing clean, organized code through proper variable management and state control

10:22

14

Swift Collections: Arrays, Sets, and Dictionaries Overview

Organize Your Data Effectively: Learn How to Choose and Optimize the Perfect Collection Type for Your SwiftUI Applications

7:00

15

Swift Arrays

Organize, transform, and display your data with Swift's most essential collection structure

8:54

16

Swift Sets

Mastering Unique Collections with Fast Lookups and Powerful Operations

11:03

17

Swift Dictionaries

Master Swift's key-value collection type with practical examples for efficient data storage, retrieval, and transformation in SwiftUI apps

18

For-Loops and Range

For-Loops and Ranges: Learn how to repeat code efficiently and work with sequences of numbers in Swift

19

Optionals

Optionals in Swift: Understanding Swift's Safety System for Handling Missing Values

20

Functions

Functions in Swift: Building Reusable Code Blocks to Organize and Streamline Your App

Meet the instructor

We all try to be consistent with our way of teaching step-by-step, providing source files and prioritizing design in our courses.

Sourasith Phomhome

UI Designer

Designer at Design+Code

icon

20 courses - 78 hours

course logo

Master Agentic Workflows

In this course, you’ll learn how to add agents to your workflows. An agent workflow is more than just a simple automation. Instead of following a fixed script, agents can make decisions, adjust to changes, and figure out the best way to complete a task. We’ll start by exploring what MCP servers are and all the new possibilities they bring. Then, we’ll dive into agentic frameworks that make it easy to build flexible, helpful agents that can take care of your everyday tasks.

2 hrs

course logo

Design Multiple Apps with Figma and AI

In this course, you’ll learn to design multiple apps using Figma and AI-powered tools, tackling a variety of real-world UI challenges. Each week, a new episode will guide you through a different design, helping you master essential UI/UX principles and workflows

4 hrs

course logo

SwiftUI Fundamentals Handbook

A comprehensive guide to mastering Swift programming fundamentals, designed for aspiring iOS developers. This handbook provides a structured approach to learning Swift's core concepts, from basic syntax to advanced programming patterns. Through carefully sequenced chapters, readers will progress from essential programming concepts to object-oriented principles, building a solid foundation for SwiftUI development. Each topic includes practical examples and clear explanations, ensuring a thorough understanding of Swift's capabilities. This handbook serves as both a learning resource and a reference guide, covering fundamental concepts required for modern iOS development. Topics are presented in a logical progression, allowing readers to build their knowledge systematically while gaining practical programming skills.

2 hrs

course logo

Design and Code User Interfaces with Galileo and Claude AI

In this course, you’ll learn how to use AI tools to make UI/UX design faster and more efficient. We’ll start with Galileo AI to create basic designs, providing a solid foundation for your ideas. Next, we’ll refine these designs in Figma to match your personal style, and finally, we’ll use Claude AI to turn them into working code—eliminating the need for traditional prototyping.

4 hrs

course logo

Build a React Native app with Claude AI

This comprehensive course explores the integration of cutting-edge AI tools into the React Native development workflow, revolutionizing the approach to mobile application creation. Participants will learn to leverage AI-powered platforms such as Claude and Locofy to expedite coding processes, enhance problem-solving capabilities, and optimize productivity.

14 hrs

course logo

Design and Prototype for iOS 18

Design and Prototype for iOS 18 is an immersive course that equips you with the skills to create stunning, user-friendly mobile applications. From mastering Figma to understanding iOS 18's latest design principles, you'll learn to craft two real-world apps - a Car Control interface and an AI assistant.

3 hrs

course logo

Master Responsive Layouts in Figma

Creating responsive layouts is a must-have skill for any UI/UX designer. With so many different devices and screen sizes, designing interfaces that look great and work well on all platforms is necessary. Mastering this skill will make you stand out in the field. In this course, we'll start from scratch to create this beautiful design using Figma. You'll learn how to make layouts that are easy to use and work well on any device. We'll cover key concepts and tools to help you master responsive design in Figma.

2 hrs

course logo

UI UX Design with Mobbin and Figma

Mobbin is a powerful tool for UI/UX designers seeking inspiration and innovative design solutions. This platform offers a vast collection of real-world mobile app designs, providing a treasure trove of UI elements and layouts.

2 hrs

course logo

3D UI Interactive Web Design with Spline

Learn to create 3D designs and UI interactions such as 3D icons, UI animations, components, variables, screen resize, scrolling interactions, as well as exporting, optimizing, and publishing your 3D assets on websites

3 hrs

course logo

Design and Prototype for iOS 17 in Figma

Crafting engaging experiences for iOS 17 and visionOS using the Figma design tool. Learn about Figma's new prototyping features, Dev Mode, variables and auto layout.

6 hrs

course logo

Design and Prototype Apps with Midjourney

A comprehensive course on transforming Midjourney concepts into interactive prototypes using essential design techniques and AI tools

8 hrs

course logo

iOS Design with Midjourney and Figma

Learn the fundamentals of App UI design and master the art of creating beautiful and intuitive user interfaces for mobile applications

1 hrs

course logo

UI Design for iOS, Android and Web in Sketch

Create a UI design from scratch using Smart Layout, Components, Prototyping in Sketch app

1 hrs

course logo

UI Design a Camera App in Figma

Design a dark, vibrant and curvy app design from scratch in Figma. Design glass icons, lens strokes and realistic buttons.

1 hrs

course logo

UI Design for iOS 16 in Sketch

A complete guide to designing for iOS 16 with videos, examples and design files

3 hrs

course logo

Prototyping in Figma

Learn the basics of prototyping in Figma by creating interactive flows from custom designs

1 hrs

course logo

UI Design Quick Websites in Figma

Learn how to design a portfolio web UI from scratch in Figma

1 hrs

course logo

UI Design Android Apps in Figma

Design Android application UIs from scratch using various tricks and techniques in Figma

2 hrs

course logo

UI Design Quick Apps in Figma

Design application UIs from scratch using various tricks and techniques in Figma

12 hrs

course logo

Figma Handbook

A comprehensive guide to the best tips and tricks in Figma

6 hrs