Design+Code logo

Quick links

Suggested search

1. Introduction to Sets in Swift

Sets are one of the fundamental collection types in Swift, yet they're often overlooked in favor of arrays and dictionaries. Understanding sets—their unique properties, performance characteristics, and ideal use cases—can dramatically improve your SwiftUI applications. This introduction will explore what sets are, why they matter specifically in SwiftUI development, and how they compare to other collection types.

A. What Are Sets?

i. Definition and core characteristics

A set in Swift is an unordered collection of unique values. This seemingly simple definition carries powerful implications for how you structure and manipulate data in your applications. The key characteristics that define sets include:

  • Uniqueness guarantee: Sets automatically ensure that each value appears only once—there can never be duplicates within a set. When you attempt to add a value that already exists, the set simply ignores the operation.
  • Unordered collection: Unlike arrays, sets don't maintain any specific sequence of elements. You cannot access elements by index, and the order may change as you add or remove elements.
  • Hashable requirement: All elements in a set must conform to the Hashable protocol. This requirement enables Swift to efficiently determine whether a value already exists in the set.
  • High-performance operations: Sets are optimized for testing whether a value exists, adding new unique values, and removing values, with these operations typically completing in constant time O(1) regardless of the set's size.

Sets exist to solve specific problems elegantly. When you work with collections where uniqueness matters more than order, or when you frequently need to check if a value exists in a collection, sets provide both conceptual clarity and performance benefits.

ii. The mental model: sets as mathematical collections

To understand sets in programming, it helps to connect them to their mathematical origins. In mathematics, a set is a collection of distinct objects considered as an object in its own right. The Swift implementation brings this mathematical construct into your code.

Think of a set as a bag where each distinct item can only appear once. You can add items to the bag or remove them, but you never need to worry about where in the bag a particular item resides—you only care whether it's in the bag or not.

This mental model reveals why sets excel at representing collections where membership is the primary concern: groups of unique identifiers, active filters in a filtering system, or categories that an item belongs to.

iii. Sets in Swift's type system

In Swift's type system, the Set type is a generic collection defined as Set<Element>, where Element must conform to the Hashable protocol. This protocol requirement enables the set to efficiently determine whether a value already exists.

Most of Swift's built-in types already conform to Hashable, including strings, integers, floating-point numbers, and booleans. For custom types, you'll need to implement the Hashable protocol, which we'll explore in a later section.

Sets are value types in Swift, meaning they're copied when assigned to a new variable or passed to a function. This behavior aligns with Swift's emphasis on value semantics, providing predictable and safe behavior in concurrent code.

B. Why Sets Matter in SwiftUI Development

Sets aren't just an abstract collection type—they solve real, practical problems in SwiftUI development that other collections can't address as elegantly.

i. Uniqueness in UI state management

SwiftUI's declarative approach to UI development is built around state management. Sets provide a natural way to manage states where uniqueness matters:

  • Selection management: When users can select multiple items, a set naturally represents the selection state, ensuring each item can only be selected once.
struct ContentView: View {
    // Using a Set to track selected item IDs ensures uniqueness
    @State private var selectedItemIDs: Set<UUID> = []
    let allItems: [Item]

    var body: some View {
        List(allItems) { item in
            HStack {
                Text(item.name)
                Spacer()
                if selectedItemIDs.contains(item.id) {
                    Image(systemName: "checkmark")
                }
            }
            .contentShape(Rectangle())
            .onTapGesture {
                // If already selected, remove it; otherwise, add it
                if selectedItemIDs.contains(item.id) {
                    selectedItemIDs.remove(item.id)
                } else {
                    selectedItemIDs.insert(item.id)
                }
            }
        }
    }
}
  • Filter state: When implementing filter systems (like in search interfaces), sets elegantly track which filters are currently active.
  • Tab uniqueness: For custom tab systems where each tab should appear exactly once, sets provide a natural data structure.

ii. Performance benefits in reactive UI

SwiftUI rebuilds views when their state changes. Using the most efficient collection type can have substantial performance implications:

  • Membership testing efficiency: When SwiftUI needs to check if an item is selected, favorited, or filtered, the contains operation on a set is dramatically faster than on an array, especially as the collection grows.
  • Change detection optimization: SwiftUI's performance depends on efficiently detecting what's changed. Sets help minimize unnecessary view updates by making it clear exactly what items are affected by a state change.
  • Reduced view rebuilding: By using sets appropriately, you can minimize unnecessary view rebuilds by ensuring that state only changes when truly needed (no duplicates being added).

iii. Data integrity in SwiftUI applications

Sets help maintain data integrity within your SwiftUI applications:

  • Preventing duplicate entries: In forms and data entry scenarios, sets ensure you don't accidentally store the same information twice.
  • Enforcing business rules: Many business domains have inherent uniqueness requirements (like unique user IDs or exclusive options) that sets naturally enforce.
  • State deduplication: When merging state from multiple sources (like local and remote data), sets automatically handle deduplication.

C. Sets vs Other Collection Types

Understanding how sets compare to arrays and dictionaries helps you choose the right collection for each situation in your SwiftUI applications.

i. Sets vs Arrays: choosing the right ordered or unordered collection

Arrays and sets serve different purposes and come with different tradeoffs:

Arrays:

  • Preserve insertion order
  • Allow duplicate elements
  • Access elements by position/index
  • Linear search performance O(n) for contains operations
  • Better when sequence matters

Sets:

  • Don't maintain any specific order
  • Guarantee uniqueness (no duplicates)
  • No direct positional access
  • Constant time O(1) for contains operations
  • Better when uniqueness and membership testing matter

Consider this performance comparison for checking if an element exists:

// With an array of 10,000 elements
let largeArray = Array(1...10000)
let startArray = Date()
let containsInArray = largeArray.contains(9876)  // O(n) - might check thousands of elements
let arrayTime = Date().timeIntervalSince(startArray)

// With a set of the same elements
let largeSet = Set(1...10000)
let startSet = Date()
let containsInSet = largeSet.contains(9876)  // O(1) - direct lookup
let setTime = Date().timeIntervalSince(startSet)

print("Array search took \(arrayTime) seconds")
print("Set search took \(setTime) seconds")
// Set search will typically be orders of magnitude faster

ii. Sets vs Dictionaries: when to use each associative collection

Sets and dictionaries are both associative collections, but they serve different needs:

Dictionaries:

  • Store key-value pairs
  • Each key maps to a specific value
  • Used when you need to retrieve values associated with keys
  • Keys must be hashable

Sets:

  • Store single values, not pairs
  • Used when you only care about whether a value exists, not what it maps to
  • Elements must be hashable

Think of a set as similar to a dictionary where you only care about the keys, not the values. In fact, Swift's implementation of sets leverages the same hash table mechanism that powers dictionaries.

iii. Practical guidelines for collection selection in SwiftUI

When developing SwiftUI applications, consider these guidelines for choosing between collection types:

Choose an Array when:

  • The order of elements matters (like items in a list that should maintain a specific sequence)
  • You need to access elements by position (first, last, or nth element)
  • Duplicate elements are valid and meaningful
  • The collection is small, or you rarely need to search for specific elements

Choose a Set when:

  • Uniqueness is required or beneficial
  • You frequently check whether a value exists in the collection
  • Order doesn't matter for how the data is used
  • You need to perform mathematical set operations (union, intersection, etc.)
  • Performance of membership testing is critical for your UI responsiveness

Choose a Dictionary when:

  • You need to associate values with unique keys
  • You need to look up values using custom identifiers
  • You're implementing a cache or memoization system
  • You need to represent a mapping relationship between two pieces of data

In SwiftUI specifically:

// Use Array when order matters
struct OrderedListView: View {
    let steps = ["Prepare ingredients", "Mix batter", "Bake", "Cool", "Serve"]

    var body: some View {
        List(steps.indices, id: \.self) { index in
            Text("\(index + 1). \(steps[index])")
        }
    }
}

// Use Set when managing unique selections
struct FilterView: View {
    @State private var activeFilters: Set<String> = []
    let availableFilters = ["Vegetarian", "Gluten-free", "Dairy-free", "Nut-free"]

    var body: some View {
        VStack {
            ForEach(availableFilters, id: \.self) { filter in
                Toggle(filter, isOn: Binding(
                    get: { activeFilters.contains(filter) },
                    set: { isActive in
                        if isActive {
                            activeFilters.insert(filter)
                        } else {
                            activeFilters.remove(filter)
                        }
                    }
                ))
            }
        }
        .padding()
    }
}

The key to mastering collection types in SwiftUI is understanding their unique characteristics and choosing the right tool for each specific scenario. Sets excel in situations where uniqueness and fast membership testing are paramount—qualities that appear frequently in reactive UI development.

2. Swift Set Fundamentals

Sets in Swift provide powerful capabilities for working with unique collections of values. Understanding how to create, modify, and manipulate sets is essential for leveraging their full potential in your SwiftUI applications. This section covers the fundamental operations and concepts you'll need to work effectively with sets.

A. Creating and Initializing Sets

Swift offers several approaches to creating and initializing sets, each suited to different scenarios. Understanding these initialization patterns helps you choose the most appropriate and expressive option for your specific needs.

i. Basic set creation syntax

The most common ways to create sets in Swift include:

// Empty set with type annotation
var emptyStringSet: Set<String> = []

// Set with initial values using array literal
let colorSet: Set<String> = ["Red", "Green", "Blue"]

// Set with type inference (requires explicit Set type)
let numberSet: Set = [1, 2, 3, 4, 5]

// Alternative syntax using the Set initializer
var anotherSet = Set<Int>()

// Creating a set from an array (removes duplicates)
let uniqueNumbers = Set([1, 2, 2, 3, 3, 3, 4, 5, 5])
// uniqueNumbers contains [1, 2, 3, 4, 5]

One important point to remember is that, unlike arrays, Swift cannot infer the type when you create a set with an array literal. You must explicitly specify that you're creating a Set:

// This creates an Array, not a Set
let colors = ["Red", "Green", "Blue"]

// This creates a Set
let colorSet: Set = ["Red", "Green", "Blue"]
// OR
let anotherColorSet = Set(["Red", "Green", "Blue"])

ii. Specialized initializers

Swift's Set type provides specialized initializers for different scenarios:

// Initialize an empty set
var emptySet = Set<Double>()

// Initialize with a sequence (like an array, range, or another collection)
let setFromRange = Set(1...5)
let setFromArray = Set(["Apple", "Banana", "Cherry"])

// Initialize with a sequence while filtering elements
let evenNumbers = Set(0...10).filter { $0 % 2 == 0 }
// evenNumbers contains [0, 2, 4, 6, 8, 10]

// Initialize with a sequence transformer
let squareSet = Set((1...5).map { $0 * $0 })
// squareSet contains [1, 4, 9, 16, 25]

// You can also combine functional approaches
let filteredTransformed = Set((1...10).filter { $0 % 2 == 1 }.map { $0 * $0 })
// Contains [1, 9, 25, 49, 81] (squares of odd numbers from 1-10)

iii. Initializing sets with custom types

When working with custom types in Swift, you need to ensure they conform to the Hashable protocol to use them in sets:

// Custom type with Hashable conformance
struct User: Hashable {
    let id: UUID
    let name: String
    let email: String

    // Swift can synthesize Hashable conformance for types with only Hashable properties
    // For custom hashing behavior, you can implement hash(into:) yourself
}

// Creating a set of custom objects
let user1 = User(id: UUID(), name: "Alice", email: "alice@example.com")
let user2 = User(id: UUID(), name: "Bob", email: "bob@example.com")
let user3 = User(id: UUID(), name: "Charlie", email: "charlie@example.com")

var userSet: Set<User> = [user1, user2, user3]

For custom types, Swift's compiler can automatically synthesize the required Hashable implementation if all stored properties are themselves Hashable. This is why simple structs with basic Swift types often work with sets without extra code.

If you need custom hashing behavior, you can implement the hash(into:) method:

struct Product: Hashable {
    let id: String
    let name: String
    let category: String
    let price: Double

    // Custom hashing that only considers the ID
    // This means two products with the same ID are considered identical
    // even if other properties differ
    func hash(into hasher: inout Hasher) {
        hasher.combine(id)
    }

    // When customizing hash(into:), you also need to implement Equatable
    static func == (lhs: Product, rhs: Product) -> Bool {
        return lhs.id == rhs.id
    }
}

iv. Practical examples in SwiftUI context

In SwiftUI applications, sets are often used to track state information:

struct FilterSelectionView: View {
    // Set to track selected filter categories
    @State private var selectedCategories: Set<String> = []
    let availableCategories = ["Electronics", "Clothing", "Books", "Home", "Sports"]

    var body: some View {
        VStack {
            Text("Select Categories")
                .font(.headline)

            ForEach(availableCategories, id: \.self) { category in
                Toggle(category, isOn: Binding(
                    get: { selectedCategories.contains(category) },
                    set: { isOn in
                        if isOn {
                            selectedCategories.insert(category)
                        } else {
                            selectedCategories.remove(category)
                        }
                    }
                ))
            }

            Text("Selected: \(selectedCategories.joined(separator: ", "))")
                .padding(.top)
        }
        .padding()
    }
}

This example demonstrates how a set naturally represents the selection state in a filter interface, ensuring each category can only be selected once.

B. Basic Set Operations

Sets provide a rich set of operations for adding, removing, and querying elements. Understanding these fundamental operations is essential for working effectively with sets in Swift.

i. Adding and removing elements

Sets provide several methods for adding and removing elements:

var fruitSet: Set<String> = ["Apple", "Banana"]

// Adding elements
fruitSet.insert("Cherry")  // Set now contains ["Apple", "Banana", "Cherry"]

// insert() returns a tuple indicating whether the insertion happened
let insertResult = fruitSet.insert("Banana")  // Banana already exists
print(insertResult.inserted)  // false (element was not inserted)
print(insertResult.memberAfterInsert)  // "Banana" (the existing element)

// Removing elements
let removedFruit = fruitSet.remove("Banana")  // Returns the removed element
print(removedFruit)  // Optional("Banana")

// If the element doesn't exist, remove() returns nil
let nonExistentRemoval = fruitSet.remove("Mango")
print(nonExistentRemoval)  // nil

// Remove all elements
fruitSet.removeAll()  // Set is now empty

The insert method is particularly useful because it returns a tuple with two values:

  • inserted: A boolean indicating whether the element was newly inserted (true) or already existed (false)
  • memberAfterInsert: The element in the set after the operation, which is either the newly inserted element or the existing one

ii. Checking membership and properties

Sets excel at quickly determining whether they contain specific elements:

let numberSet: Set = [1, 2, 3, 4, 5]

// Check if an element exists
let containsThree = numberSet.contains(3)  // true
let containsSix = numberSet.contains(6)    // false

// Check if set is empty
let isEmpty = numberSet.isEmpty  // false

// Get the count of elements
let count = numberSet.count  // 5

// Get the first element (order is not guaranteed)
if let firstElement = numberSet.first {
    print(firstElement)  // Prints one of the elements (order not guaranteed)
}

The contains method is exceptionally fast in sets (O(1) constant time complexity), making it ideal for frequent membership testing in performance-critical code.

iii. Iterating through sets

Although sets are unordered, you can still iterate through their elements:

let fruitSet: Set = ["Apple", "Banana", "Cherry", "Date"]

// Basic iteration
for fruit in fruitSet {
    print(fruit)
}

// Using forEach
fruitSet.forEach { fruit in
    print("I like \(fruit)")
}

// Convert to array for sorted iteration
for fruit in fruitSet.sorted() {
    print(fruit)
}

// Filter during iteration
for fruit in fruitSet where fruit.count > 5 {
    print("\(fruit) has more than 5 letters")
}

Remember that the iteration order is not guaranteed to be the same across multiple runs or after modifications to the set. If you need a consistent order, convert the set to an array and sort it.

iv. Set algebra operations

One of the most powerful aspects of sets is their ability to perform mathematical set operations:

let setA: Set = [1, 2, 3, 4, 5]
let setB: Set = [4, 5, 6, 7, 8]

// Union: all elements that are in either set
let union = setA.union(setB)
// [1, 2, 3, 4, 5, 6, 7, 8]

// Intersection: only elements that are in both sets
let intersection = setA.intersection(setB)
// [4, 5]

// Difference: elements in setA that aren't in setB
let difference = setA.subtracting(setB)
// [1, 2, 3]

// Symmetric difference: elements in either set but not in both
let symmetricDifference = setA.symmetricDifference(setB)
// [1, 2, 3, 6, 7, 8]

These operations create new sets without modifying the original sets. For in-place mutations, use their "form" counterparts:

var mutableSet: Set = [1, 2, 3]

// In-place union
mutableSet.formUnion([3, 4, 5])
// mutableSet now contains [1, 2, 3, 4, 5]

// In-place intersection
mutableSet.formIntersection([2, 4, 6])
// mutableSet now contains [2, 4]

// In-place subtraction
mutableSet.subtract([2, 3, 4])
// mutableSet now contains [4]

// In-place symmetric difference
mutableSet.formSymmetricDifference([4, 5, 6])
// mutableSet now contains [5, 6]

v. Set comparison operations

Sets provide methods to compare their relationship with other sets:

let superSet: Set = [1, 2, 3, 4, 5]
let subSet: Set = [3, 4]
let overlappingSet: Set = [4, 5, 6]
let disjointSet: Set = [7, 8, 9]

// Check if a set is a subset of another
print(subSet.isSubset(of: superSet))  // true

// Check if a set is a superset of another
print(superSet.isSuperset(of: subSet))  // true

// Check if a set is a strict subset (subset but not equal)
print(subSet.isStrictSubset(of: superSet))  // true
print(superSet.isStrictSubset(of: superSet))  // false

// Check if a set is a strict superset (superset but not equal)
print(superSet.isStrictSuperset(of: subSet))  // true
print(superSet.isStrictSuperset(of: superSet))  // false

// Check if sets have elements in common
print(superSet.isDisjoint(with: disjointSet))  // true (no common elements)
print(superSet.isDisjoint(with: overlappingSet))  // false (have common elements)

These comparison operations are particularly useful when implementing logic that depends on the relationship between different sets, such as checking if a user has all required permissions or if two feature sets overlap.

C. Mutability and Immutability

Understanding how mutability works with sets in Swift is crucial for creating robust and predictable code, especially in the context of SwiftUI's state management.

i. Mutable vs. immutable sets

Like other Swift types, sets can be declared as either mutable or immutable:

// Immutable set (cannot be modified after creation)
let immutableSet: Set = [1, 2, 3]

// Attempting to modify an immutable set causes a compiler error
// immutableSet.insert(4)  // Error: Cannot use mutating member on immutable value

// Mutable set (can be modified after creation)
var mutableSet: Set = [1, 2, 3]
mutableSet.insert(4)  // Works fine

This distinction is enforced at compile time, helping you express your intent about whether a set should be changeable and preventing accidental modifications.

ii. Value semantics and copying behavior

Sets in Swift have value semantics, meaning they are copied when assigned to a new variable or passed to a function:

var originalSet: Set = [1, 2, 3]
var copiedSet = originalSet  // Creates a copy, not a reference

// Modifying the copy does not affect the original
copiedSet.insert(4)
print(originalSet)  // [1, 2, 3]
print(copiedSet)    // [1, 2, 3, 4]

This behavior is particularly important in SwiftUI, which relies on value semantics for its state management system:

struct ContentView: View {
    @State private var selectedTags: Set<String> = ["Swift", "SwiftUI"]

    var body: some View {
        VStack {
            // When this button is tapped, a copy of the set is modified,
            // which then triggers a view update due to @State
            Button("Add Tag") {
                selectedTags.insert("iOS")
            }

            List(Array(selectedTags), id: \.self) { tag in
                Text(tag)
            }
        }
    }
}

Value semantics ensure that modifications create new values rather than changing shared state, which helps prevent subtle bugs in concurrent or reactive code.

iii. Sets in SwiftUI state management

SwiftUI's state management system works seamlessly with sets thanks to their value semantics:

struct TagSelectionView: View {
    // Using Set for state management
    @State private var selectedTags: Set<String> = []
    let availableTags = ["Swift", "SwiftUI", "iOS", "macOS", "watchOS", "tvOS"]

    var body: some View {
        VStack {
            Text("Selected: \(selectedTags.count) tags")
                .font(.headline)

            ScrollView {
                LazyVGrid(columns: [GridItem(.adaptive(minimum: 100))]) {
                    ForEach(availableTags, id: \.self) { tag in
                        TagView(
                            tag: tag,
                            isSelected: selectedTags.contains(tag),
                            onTap: {
                                // Toggle selection state
                                if selectedTags.contains(tag) {
                                    selectedTags.remove(tag)
                                } else {
                                    selectedTags.insert(tag)
                                }
                            }
                        )
                    }
                }
                .padding()
            }

            Button("Clear All") {
                selectedTags.removeAll()
            }
            .disabled(selectedTags.isEmpty)
            .padding()
        }
    }
}

struct TagView: View {
    let tag: String
    let isSelected: Bool
    let onTap: () -> Void

    var body: some View {
        Text(tag)
            .padding(.horizontal, 12)
            .padding(.vertical, 6)
            .background(isSelected ? Color.blue : Color.gray.opacity(0.3))
            .foregroundColor(isSelected ? .white : .primary)
            .cornerRadius(20)
            .onTapGesture(perform: onTap)
    }
}

When the user selects or deselects a tag, the state is updated by modifying the set, which then triggers a view update. The set ensures that each tag can only be selected once, naturally enforcing the uniqueness constraint.

iv. Functional programming with sets

Sets support functional programming patterns through higher-order functions:

let numberSet: Set = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

// Map: transform all elements (returns an array, not a set)
let doubled = numberSet.map { $0 * 2 }
// [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]

// To get a Set result, create a new set from the transformed elements
let doubledSet = Set(numberSet.map { $0 * 2 })

// Filter: get elements matching a condition
let evenNumbers = numberSet.filter { $0 % 2 == 0 }
// [2, 4, 6, 8, 10]

// Reduce: combine all elements into a single value
let sum = numberSet.reduce(0, +)
// 55

// CompactMap: transform and filter out nil results
let strings: Set = ["1", "2", "three", "4", "five"]
let validNumbers = Set(strings.compactMap { Int($0) })
// [1, 2, 4]

Note that map and filter return arrays, not sets. If you want a set result, you need to create a new set from the result. This is because the transformed elements might not be unique or might not conform to Hashable.

v. Thread safety and concurrency considerations

When working with sets in concurrent code, the value semantics of sets provide important safety guarantees:

let originalSet: Set = [1, 2, 3]

// In concurrent code, each closure gets its own copy
DispatchQueue.global().async {
    var localCopy = originalSet
    localCopy.insert(4)
    print("Async: \(localCopy)")
}

// Original remains unchanged
print("Original: \(originalSet)")

This becomes particularly important in SwiftUI applications where views might be updated from background threads, such as when receiving network responses or processing data. The value semantics ensure that concurrent modifications don't lead to race conditions or unexpected behavior.

In SwiftUI views, you can generally rely on SwiftUI's state management to handle updates safely. However, when working with sets in actor-based concurrency or other multithreaded contexts, be mindful of how and where modifications happen:

actor TagStore {
    private var tags: Set<String> = []

    func addTag(_ tag: String) {
        tags.insert(tag)
    }

    func removeTag(_ tag: String) {
        tags.remove(tag)
    }

    func getAllTags() -> Set<String> {
        // Returns a copy due to value semantics
        return tags
    }
}

// When using TagStore in SwiftUI
struct TagManagerView: View {
    let tagStore: TagStore
    @State private var localTags: Set<String> = []
    @State private var newTag = ""

    var body: some View {
        VStack {
            // UI elements...

            Button("Refresh Tags") {
                Task {
                    // Safely update state from async context
                    localTags = await tagStore.getAllTags()
                }
            }
        }
        .task {
            // Load initial data
            localTags = await tagStore.getAllTags()
        }
    }
}

The value semantics of sets work harmoniously with Swift's concurrency model, making them a safe choice for shared state in concurrent code. Understanding these fundamental aspects of sets in Swift—creating them, performing basic operations, and managing mutability—provides the foundation for using sets effectively in your SwiftUI applications.

3. Working with Sets in SwiftUI

Sets provide powerful capabilities specifically suited to SwiftUI development, offering elegant solutions for state management, data organization, and performance optimization. In this section, we'll explore practical applications of sets within the SwiftUI framework, showing how they can enhance your applications' functionality, maintainability, and performance.

Open the source code for real examples generated with AI.

A. Data Management with Sets

SwiftUI applications frequently need to manage collections of unique data efficiently. Sets provide natural solutions for many common data management scenarios.

i. Managing unique identifiers (Demo 3Ai)

This SwiftUI application demonstrates an efficient approach to product catalog management by combining an array with a Set data structure. This implementation pattern elegantly addresses a common performance challenge in collection management by using the appropriate data structure for each operation—maintaining ordered iteration through arrays while achieving constant-time lookups through Sets.

ii. Tag and category systems (Demo 3Aii)

This SwiftUI application demonstrates an elegant tag-based filtering system using Sets for efficient content management. This implementation showcases how Sets provide both performance benefits and semantic clarity when working with collections where uniqueness and membership testing are primary operations.

iii. Deduplication in data pipelines (Demo 3Aiii)

This SwiftUI application showcases an advanced data processing system that leverages Sets for efficient deduplication across multiple data sources. This educational implementation clearly illustrates how Sets excel at handling scenarios requiring uniqueness guarantees and membership testing across large collections, while the detailed logging system helps visualize the efficiency gains compared to array-only approaches that would require costly linear searches to identify duplicates.

iv. Data validation and integrity (Demo 3Aiv)

This SwiftUI application demonstrates an advanced form validation system that leverages Sets for efficient data validation and error handling. This practical demonstration illustrates how Sets provide both semantic clarity and significant performance advantages over arrays when validating form data, especially when working with predefined collections of restricted values or when checking for missing required fields.

B. State Management Using Sets

SwiftUI's reactive programming model works exceptionally well with sets for managing application state, especially for selection states, filters, and toggles.

i. Managing multi-selection state (Demo 3Bi)

This SwiftUI application demonstrates a sophisticated multi-selection implementation that leverages Sets for optimal performance and clean code structure. The demo provides both practical functionality through the interactive UI and theoretical understanding through the informational view, effectively teaching users not just how to implement multi-selection but why Sets are the optimal data structure for this common UI pattern. This pattern is immediately applicable in many real-world scenarios like filtering interfaces, batch operations on collections, and any situation requiring efficient state tracking of selected items.

ii. Filter state management (Demo 3Bii)

This SwiftUI application demonstrates a practical product filtering system that leverages Sets for efficient filter state management across multiple criteria. The code includes both a standalone filter view and an interactive demo with pre-selected filters that demonstrates real-world data binding, showcasing how Sets provide a clean, performant solution for managing multi-criteria filtering interfaces. This pattern is directly applicable to e-commerce applications, content libraries, or any interface requiring users to filter collections by multiple overlapping criteria, with Sets ensuring the UI remains responsive even as the number of filter options or selected items grows.

iii. Toggle groups and mutually exclusive options (Demo 3Biii)

This SwiftUI code implements an advanced settings management system that transcends traditional feature toggling by leveraging Swift's powerful type system and Set operations. The approach demonstrates a nuanced state management technique that provides users with a flexible, intuitive settings experience while maintaining robust programmatic control, making it an exemplary implementation of declarative UI design in SwiftUI that can be easily adapted to various complex application scenarios.

iv. Permissions and capability management (Demo 3Biv)

This SwiftUI code demonstrates a sophisticated role-based access control (RBAC) system that leverages Swift's powerful type system and Set operations to manage user permissions with exceptional flexibility and granularity. The implementation introduces a comprehensive permission management model through three key components: a Permission enum defining granular access rights, a Role struct representing predefined permission sets, and a User class that dynamically calculates effective permissions by combining assigned roles and custom permissions. Sets offer powerful performance advantages that can significantly improve SwiftUI applications. While some performance optimizations might seem subtle, they become critical as your application scales or when dealing with large datasets. Understanding these optimization techniques can help you create more responsive and efficient SwiftUI interfaces.

C. Performance Optimization with Sets

Sets offer powerful performance advantages that can significantly improve SwiftUI applications. While some performance optimizations might seem subtle, they become critical as your application scales or when dealing with large datasets. Understanding these optimization techniques can help you create more responsive and efficient SwiftUI interfaces.

i. Efficient membership testing

Sets excel at quickly determining whether they contain a specific element. This operation occurs in constant time (O(1)) regardless of the set's size, unlike arrays which require linear time (O(n)) to search through all elements.

In SwiftUI, this becomes particularly valuable when checking if an item is selected, favorited, or belongs to a specific category. For instance, when rendering a list with hundreds of items where some are selected, checking selection status with a set is dramatically faster than searching through an array of selected items. As your application scales, this performance difference becomes increasingly noticeable, potentially preventing UI lag and ensuring smooth animations.

The performance impact is most significant in operations that run frequently, such as during scrolling or when filtering large datasets where membership tests must be performed repeatedly.

ii. Caching and memoization

Sets provide an elegant solution for implementing caching mechanisms to avoid redundant operations. By tracking which operations have already been performed or which resources have been loaded, you can prevent unnecessary work.

In SwiftUI applications, this pattern helps avoid recomputing values, re-fetching resources, or redrawing views that haven't changed. For example, you can use sets to track which images have already been loaded, which calculations have been performed, or which network requests have been initiated.

The result is decreased CPU usage, reduced memory consumption, lower battery drain, and a more responsive user interface. This is especially valuable in resource-intensive applications or when working with limited device capabilities. Proper caching with sets helps establish clear boundaries around expensive operations, ensuring they only execute when absolutely necessary.

iii. Preventing redundant calculations and view updates

SwiftUI's reactive programming model updates the UI whenever state changes. By using sets to precisely track which items need updates, you can minimize unnecessary view recalculations and redraws.

Rather than marking entire collections as needing updates, sets allow you to pinpoint exactly which items have changed. This selective approach to updates means SwiftUI only needs to regenerate the relevant portions of the view hierarchy. The result is more efficient rendering and smoother animations, particularly when working with complex interfaces or large datasets.

This granular tracking becomes especially important in applications with real-time data updates, collaborative features, or complex state management where being precise about what needs to change directly impacts application performance.

iv. Optimizing search and filtering operations

Sets enable highly efficient search and filtering implementations that remain performant even as data volumes grow. By using inverted indices (where each filter value maps to a set of matching item IDs) and leveraging set operations like union and intersection, you can implement sophisticated filtering systems that scale beautifully.

For example, when implementing a product catalog with multiple filter criteria (price, category, brand, etc.), sets allow you to quickly combine these criteria through mathematical set operations. This approach maintains consistent performance regardless of how many filters are applied, unlike array-based filtering which becomes increasingly expensive with each additional criterion.

These optimized filtering techniques are particularly valuable for search interfaces, data visualization components, and any feature where users need to quickly narrow down large datasets according to multiple criteria.

4. Advanced Set Operations

Sets in Swift offer powerful capabilities beyond basic operations. When you master advanced set operations, you can solve complex problems with elegant, efficient code. This section explores sophisticated aspects of sets that can elevate your SwiftUI applications.

A. Set Algebra (Union, Intersection, etc.)

Set algebra operations allow you to perform mathematical operations on sets that produce new sets based on specific relationships. These operations form the foundation for many advanced algorithms and data processing techniques.

i. Understanding mathematical set operations

Set algebra is rooted in mathematical set theory, providing powerful operations for combining and comparing sets. Swift implements these operations with highly optimized code.

The fundamental set operations include:

Union (∪): Combines elements from both sets, removing duplicates automatically. This operation creates a new set containing all elements that appear in either or both sets.

Intersection (∩): Creates a new set containing only elements that appear in both sets. This finds the common elements between two sets.

Difference (−): Creates a new set containing elements from the first set that don't appear in the second set. This essentially "subtracts" one set from another.

Symmetric Difference (△): Creates a new set containing elements that appear in either set but not in both. This is equivalent to the union minus the intersection.

For example, imagine you have two sets of fruits:

let basketA: Set = ["Apple", "Banana", "Orange", "Pear"]
let basketB: Set = ["Banana", "Grapefruit", "Orange", "Kiwi"]

The union would be all fruits from both baskets without duplicates: ["Apple", "Banana", "Orange", "Pear", "Grapefruit", "Kiwi"].

The intersection would be only fruits present in both baskets: ["Banana", "Orange"].

ii. Set operations in Swift syntax

Swift provides both non-mutating methods that return new sets and mutating methods that modify sets in place.

Non-mutating operations:

// Union: all fruits from either basket
let allFruits = basketA.union(basketB)
// ["Apple", "Banana", "Orange", "Pear", "Grapefruit", "Kiwi"]

// Intersection: fruits common to both baskets
let commonFruits = basketA.intersection(basketB)
// ["Banana", "Orange"]

// Difference: fruits in A but not in B
let onlyInA = basketA.subtracting(basketB)
// ["Apple", "Pear"]

// Symmetric difference: fruits in either basket but not both
let uniqueFruits = basketA.symmetricDifference(basketB)
// ["Apple", "Pear", "Grapefruit", "Kiwi"]

Mutating operations modify the set in place, which is more efficient when you don't need the original set:

var fruitsCollection: Set = ["Apple", "Banana"]
fruitsCollection.formUnion(["Banana", "Kiwi"])
// fruitsCollection is now ["Apple", "Banana", "Kiwi"]

iii. Set algebra for solving complex problems

Set algebra provides elegant solutions for many challenging problems in SwiftUI applications:

Finding items matching multiple criteria: For example, if you have sets of products that are "on sale" and products that are "in stock", you can find products that are both on sale AND in stock with a simple intersection:

let onSaleProducts: Set = [1001, 1002, 1003, 1005]
let inStockProducts: Set = [1002, 1003, 1004, 1006]

// Products both on sale AND in stock
let availableDeals = onSaleProducts.intersection(inStockProducts)
// [1002, 1003]

Creating composite filters: Union operations allow you to combine filter criteria with OR logic:

let featuredProducts: Set = [2001, 2003, 2005]
let newArrivals: Set = [2002, 2004, 2005]

// Products that are either featured OR new arrivals
let promotedProducts = featuredProducts.union(newArrivals)
// [2001, 2002, 2003, 2004, 2005]

B. Working with Custom Types in Sets

Making your custom types work with sets requires understanding how sets determine equality and calculate hash values.

i. Hashable protocol requirements and implementation

To store a custom type in a set, it must conform to the Hashable protocol, which inherits from Equatable. This means implementing:

  1. The == operator to define when two instances are considered equal
  2. A method to generate a hash value for each instance

A simple example with a custom User type:

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

    // Two users are equal if they have the same ID
    static func == (lhs: User, rhs: User) -> Bool {
        return lhs.id == rhs.id
    }

    // Only hash based on the id property
    func hash(into hasher: inout Hasher) {
        hasher.combine(id)
    }
}

// Now we can use User in a Set
var users: Set<User> = []
users.insert(User(id: 1, name: "Alice", email: "alice@example.com"))

ii. Auto-synthesis of Hashable for structs and enums

Swift can automatically generate Hashable conformance for structs and enums if all their stored properties or associated values are themselves Hashable:

// No manual implementation needed - Swift generates it
struct Product: Hashable {
    let id: UUID
    let name: String
    let price: Double
}

// Now we can create a set of products
let featuredProducts: Set<Product> = [
    Product(id: UUID(), name: "Phone", price: 999),
    Product(id: UUID(), name: "Tablet", price: 499)
]

iii. Custom hashing implementation

Sometimes you'll want to customize how hashing works, particularly when only certain properties should determine identity:

struct DatabaseEntity: Hashable {
    let id: Int
    var name: String
    var lastModified: Date
    var data: [String: Any]

    // Two entities are the same if they have the same ID,
    // regardless of other properties
    static func == (lhs: DatabaseEntity, rhs: DatabaseEntity) -> Bool {
        return lhs.id == rhs.id
    }

    // Only hash based on the id
    func hash(into hasher: inout Hasher) {
        hasher.combine(id)
    }
}

// This allows us to find entities by ID in a set, even if other properties differ
var entities: Set<DatabaseEntity> = [/* ... */]
let searchID = 42
if let entity = entities.first(where: { $0.id == searchID }) {
    print("Found entity: \(entity.name)")
}

C. Sets for Complex Data Filtering

Sets truly shine when implementing sophisticated filtering mechanisms. Their mathematical operations provide elegant solutions for complex filtering logic.

i. Building complex filter expressions with set operations

Set operations naturally express boolean logic for filtering:

// Define sets of product IDs for various criteria
let categoryA: Set = [1, 2, 3, 4]
let categoryB: Set = [3, 4, 5, 6]
let onSale: Set = [2, 4, 6, 8]
let outOfStock: Set = [1, 6, 9]

// Products in either category A OR category B
let categoryAOrB = categoryA.union(categoryB)
// [1, 2, 3, 4, 5, 6]

// Products that are (in category A OR B) AND on sale
let onSaleInCategories = categoryAOrB.intersection(onSale)
// [2, 4, 6]

// Products that are (in category A OR B) AND on sale BUT NOT out of stock
let availableDeals = onSaleInCategories.subtracting(outOfStock)
// [2, 4]

This declarative approach is more readable than complex conditional logic and scales beautifully as filtering criteria grow.

ii. Implementing advanced filtering patterns

Sets enable sophisticated filtering patterns in SwiftUI applications, such as faceted search where some filters combine as AND conditions and others as OR conditions:

// Color filters (OR relationship within category)
let redProducts: Set = [101, 102, 105]
let blueProducts: Set = [103, 104, 105]
let selectedColors: Set = [redProducts, blueProducts].reduce(into: Set<Int>()) { 
    $0.formUnion($1) 
}
// [101, 102, 103, 104, 105]

// Size filters (OR relationship within category)
let smallProducts: Set = [101, 103, 106]
let mediumProducts: Set = [102, 104, 105]
let selectedSizes: Set = [smallProducts, mediumProducts].reduce(into: Set<Int>()) { 
    $0.formUnion($1) 
}
// [101, 102, 103, 104, 105, 106]

// Between different categories, we apply AND logic
let filteredProducts = selectedColors.intersection(selectedSizes)
// [101, 102, 103, 104, 105]

iii. Optimizing multi-criteria filtering

For highly optimized filtering, we can build inverted indices that map attributes to sets of items with those attributes:

// Inverted indices mapping attributes to product IDs
let productsByCategory: [String: Set<Int>] = [
    "electronics": [1, 2, 3, 4],
    "clothing": [5, 6, 7],
    "books": [8, 9, 10]
]

let productsByBrand: [String: Set<Int>] = [
    "apple": [1, 3],
    "samsung": [2, 4],
    "nike": [5, 6]
]

// Find products that are in electronics category AND made by samsung
if let electronicsProducts = productsByCategory["electronics"],
   let samsungProducts = productsByBrand["samsung"] {
    let samsungElectronics = electronicsProducts.intersection(samsungProducts)
    // [2, 4]
}

iv. Real-world filtering example: Tag-based content filtering

A common real-world example is filtering content by tags, where users can select multiple tags and see items that match all their selections:

// Content items with tags
struct ContentItem: Identifiable {
    let id: Int
    let title: String
    let tags: Set<String>
}

let library = [
    ContentItem(id: 1, title: "SwiftUI Basics", tags: ["iOS", "SwiftUI", "Beginner"]),
    ContentItem(id: 2, title: "Advanced Animations", tags: ["iOS", "SwiftUI", "Advanced"]),
    ContentItem(id: 3, title: "Core Data Fundamentals", tags: ["iOS", "Data", "Beginner"]),
    ContentItem(id: 4, title: "SwiftUI Architecture", tags: ["iOS", "SwiftUI", "Architecture"])
]

// User selected tags (we want items that have ALL these tags)
let selectedTags: Set<String> = ["iOS", "SwiftUI"]

// Filter content items that have all selected tags
let filteredContent = library.filter { item in
    selectedTags.isSubset(of: item.tags)
}
// Returns items 1, 2, and 4

// We could also find items that match ANY selected tag
let anyTagMatches = library.filter { item in
    !selectedTags.isDisjoint(with: item.tags)
}
// Returns all items

5. Conclusion

Sets are a powerful and often underutilized tool in Swift development. Throughout this tutorial, we've explored how sets provide unique capabilities that can transform your approach to SwiftUI development—enabling more efficient code, clearer intent, and better performance for many common scenarios.

Sets embody an elegant concept: collections of unique values without defined order, optimized for membership testing and set-theoretic operations. This seemingly simple definition unlocks sophisticated capabilities that can significantly enhance your SwiftUI applications.

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 Arrays: Best Practices in SwiftUI Part 2

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

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

15

Swift Arrays: The Basics Part 1

Master essential array operations and manipulations to build a solid foundation for iOS development

16

Swift Arrays: Best Practices in SwiftUI Part 2

Integrate arrays with SwiftUI, optimize performance, and implement professional-grade patterns for production apps

17

Swift Sets

From Basics to Advanced: Swift Set Techniques for Better Apps

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 - 77 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.

1 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