100 Days of SwiftUI Checkpoint 2

Course Credit:
This blog post's content is based on the fantastic learning materials from Paul Hudson's 100 Days of SwiftUI course, available for free at Hacking with Swift.
Data Structures in Swift: Arrays, Dictionaries, Sets, and Enums
As you dive deeper into Swift development, you'll quickly find yourself needing to store and manage collections of data. Swift provides three primary collection types—arrays, dictionaries, and sets—each designed for specific use cases. On top of that, enums allow you to define your own custom data types, bringing clarity and safety to your code.
Let's break down these essential tools and recap what makes them so valuable.
Arrays: Your Go-To for Ordered Collections
Arrays are perhaps the most frequently used data structure in Swift, and for good reason! They allow you to store multiple values of the same type in a single, ordered list. Each item in an array has an integer index, starting from zero, which you can use to read values back out.
Think of an array as a numbered list:
- Specialized: An array of strings can only hold strings; an array of integers can only hold integers.
- Ordered: The order in which you add items is preserved.
- Feature-rich: Arrays come with helpful functionality like
count
(to see how many items it holds),append
(to add new items),contains
(to check if an item exists),remove(at:)
(to delete items by index), andsorted()
(to get a new array with items in sorted order).
You'll find yourself reaching for arrays constantly in your Swift projects. They are the workhorses of data storage.
Dictionaries: When You Need Key-Value Pairs
While arrays use numerical indices, dictionaries allow you to store and retrieve values using keys you define. These keys are typically strings, but they can be any type that conforms to Hashable
(which most standard Swift types do). Just like arrays, dictionaries must be specialized, meaning you define the type for both the key and the value.
Imagine a dictionary as a real-world dictionary: you look up a word (the key) to find its definition (the value).
- Specialized: You define the type of the key and the type of the value (e.g.,
[String: Int]
for string keys and integer values). - Unordered: The order in which you add items to a dictionary is not preserved.
- Flexible Access: You access values using their associated keys, making it incredibly intuitive to retrieve specific pieces of data.
- Helpful Functionality: Dictionaries also offer
count
,contains
, and the ability to provide adefault
value if a key doesn't exist, preventing crashes.
Dictionaries are perfect when you need to store data that has a meaningful identifier, like user profiles, configuration settings, or lookup tables.
Sets: For Unique, Unordered Collections
Sets are a third way to store collections of values, but they have a distinct advantage: they only store unique values. If you try to add a duplicate item to a set, it simply won't be added. Like arrays and dictionaries, sets are specialized, holding only one specific type of data.
What makes sets special is their underlying optimization for checking whether an item exists within the collection.
- Unique Values: No duplicates allowed.
- Unordered: The order of items in a set is not guaranteed; Swift stores them in an optimized way for performance.
- Extremely Fast Lookups: If you frequently need to check if an item is present in a large collection, a set is often the fastest way to do it.
While you won't use sets as often as arrays or dictionaries, they are incredibly powerful when you need to manage unique items and perform rapid existence checks.
Enums: Creating Your Own Specific Types
Beyond just grouping existing data types, Swift's enumerations (enums) let you define your own custom types with a finite, specific set of related values. This is incredibly powerful for adding clarity and safety to your code by limiting the possible values a variable can hold.
Consider actions a user can perform, types of notifications, or days of the week. Instead of using raw strings or integers that could lead to typos or invalid values, enums provide a type-safe way to represent these options.
- Type Safety: Prevents you from assigning invalid values.
- Readability: Makes your code more understandable by using meaningful names for values.
- Completeness: Ensures you've handled all possible cases when working with an enum (especially useful with
switch
statements).
Enums are fantastic for modeling states, categories, or choices within your program, making your code more robust and easier to maintain.
Type Inference vs. Type Annotation: Swift's Smart Defaults and Your Explicit Control
Swift is incredibly intelligent when it comes to figuring out the type of data you're working with. This is called type inference, and it means you often don't need to explicitly state a variable's type.
For example, if you write let name = "Alice"
, Swift automatically infers that name
is a String
. This keeps your code concise and readable.
However, there are times when you'll want to be explicit about a variable's type, and that's where type annotationcomes in. You explicitly tell Swift what type a variable should be: let score: Int = 0
.
Type annotation is particularly useful in situations like:
- Creating empty collections:
var names: [String] = []
- Declaring a variable without an initial value:
var username: String
- Overriding Swift's default inference: If Swift infers a type that isn't precisely what you intend.
- Improving code clarity: Sometimes, being explicit helps others (and your future self!) understand your code better.
Prioritizing Your Data Structures
When it comes to frequency of use, you'll find yourself reaching for arrays by far the most. They are fundamental to almost every Swift application. Dictionaries come in a strong second, essential for working with structured data that needs identifiable keys. Sets, while powerful for specific use cases like ensuring uniqueness or performing fast lookups, are used less frequently than arrays and dictionaries.
Understanding these core data structures and when to use each one is a cornerstone of effective Swift programming. They provide the building blocks for managing and manipulating information efficiently in your applications.
import UIKit
// --- Arrays: Ordered Collections of Values ---
// Arrays store a list of items of the same type.
// You can create an array with a list of values.
var beatles = ["John", "Paul", "George"]
print(beatles)
// You can access elements using their zero-based index.
print(beatles[0])
// You can add new elements to an array using `append()`.
beatles.append("Ringo")
beatles.append("Adrian")
print(beatles)
print(beatles[3])
// Arrays can hold different data types, like `Double`.
var temperatures = [25.3, 28.2, 26.4]
let numbers: [Double] = [4, 8, 15, 16, 23, 42]
print(temperatures)
// You can combine values from different arrays.
let firstNumber = numbers[0]
let secondtemp = temperatures[1]
let value = firstNumber + secondtemp
print(value)
// You can create an empty array using different syntax.
// A common way is with `Array<Type>()`.
var scores = Array<Int>()
scores.append(100)
scores.append(90)
scores.append(80)
print(scores[1])
var albums = Array<String>()
albums.append("Abbey Road")
albums.append("Please Please Me")
print(albums)
// A more concise way to create an empty array.
var scores1 = [Int]()
scores1.append(101)
scores1.append(91)
scores1.append(81)
print(scores1[1])
var albums1 = [String]()
albums1.append("Folklore")
albums1.append("Fearless")
print(albums1)
// You can also create an array with a single initial value.
var albums2 = ["The Wall"]
print(albums2)
// `count` tells you how many items are in an array.
print(albums2.count)
var characters = ["Lana", "Barbie", "Micheal", "Jenny"]
print(characters.count)
// You can remove elements by their index.
characters.remove(at: 2)
print(characters)
// `removeAll()` clears all elements from an array.
characters.removeAll()
print(characters)
// `contains()` checks if an array has a specific element.
let bondMovies = ["Casino Royale", "Spectre", "No Time to Die"]
print(bondMovies.contains("Frozen"))
// `sorted()` returns a new array with the elements in sorted order.
let cities = ["London", "Tokyo", "Rome", "Budapest"]
print(cities.sorted())
// `reversed()` returns a view of the array in reverse order.
let colors = ["red", "blue", "green", "yellow"]
let reversedColors = colors.reversed()
print(reversedColors)
// --- Dictionaries: Storing Data with Keys ---
// Dictionaries store key-value pairs.
// Using an array for key-value data is problematic.
var employee = ["Taylor Swift", "Singer", "Nashville"]
print("Name: \(employee[0])")
print("Job Title: \(employee[1])")
print("Location: \(employee[2])")
// A dictionary is the better way to store this kind of data.
// It uses a key (e.g., "Name") to find a value (e.g., "Taylor Swift").
let employee2 = [
"Name": "Taylor Swift",
"Job Title": "Singer",
"Location": "Nashville"
]
print(employee2)
// You can access values using their keys.
// Using `default` provides a fallback value if the key doesn't exist.
print(employee2["Name", default: "Unknown"])
print(employee2["Job Title", default: "Unknown"])
print(employee2["Location", default: "Unknown"])
// Dictionary keys and values can be of different types.
let hasGraduated = [
"Eric": false,
"Meave": true,
"Otis": false
]
let olympics = [
2012: "London",
2016: "Rio de Janeiro",
2020: "Tokyo"
]
print(hasGraduated["Meave", default: false])
print(olympics[2012, default: "Unknown"])
// You can create an empty dictionary and add values to it.
var heights = [String: Int]()
heights["Yao Ming"] = 229
heights["Shaquille O'Neal"] = 216
heights["LeBron James"] = 206
print(heights)
var archEnemies = [String: String]()
archEnemies["Batman"] = "The Joker"
archEnemies["Superman"] = "Lex Luthor"
print(archEnemies)
// --- Sets: Unordered Collections for Unique Values ---
// Sets are used for fast data lookup, and they only store unique values.
// The order of items in a set is not guaranteed.
let actors = Set([
"Jodie Foster",
"Denzel Washington",
"Tom Cruise",
"Cate Blanchett"
])
print(actors)
// You can create an empty set and insert values.
var actors2 = Set<String>()
actors2.insert("Jack Nicholson")
actors2.insert("Robin Williams")
actors2.insert("Emma Stone")
actors2.insert("Amy Adams")
print(actors2)
// --- Enums: Defining a Set of Related Values ---
// Enums, or enumerations, let you define your own data types
// with a fixed number of possible values.
var selected = "Monday"
selected = "Tuesday"
selected = "January" // `January` doesn't fit with `Monday` and `Tuesday`.
selected = "Friday"
// An enum is a better way to represent a fixed set of choices.
enum Weekday {
case monday
case tuesday
case wednesday
case thursday
case friday
}
// You can now declare variables of type `Weekday`.
var day = Weekday.monday
day = .tuesday // The type is inferred, so you can use the shorter `.tuesday` syntax.
day = .wednesday
// You can define multiple cases on a single line.
enum Month {
case january, february, march, april, may, june, july, august, september, october, november, december
}
var month: Month = .january
month = .february
month = .march
// Another enum example.
enum UIStyle {
case light, dark, system
}
var style: UIStyle = .light
style = .dark
// --- Type Annotations: Being Explicit About Data Types ---
// Swift uses type inference to figure out the data type of a variable.
// This is often called "implicit" typing.
// let surname = "Lasso" // Swift infers `String`.
// var score = 0 // Swift infers `Int`.
// `Type annotation` is when you explicitly state the type.
let surname: String = "Lasso"
var score: Int = 0
let playerName: String = "John Appleseed"
let playerScore: Int = 1000
let pi: Double = 3.14
var isGameOver: Bool = false
// Type annotations are helpful for empty arrays, dictionaries, and sets.
var albums4: [String] = ["Red", "Fearless"]
var users4: [String: Int] = ["Alice": 123, "Bob": 456]
var books4: Set<String> = Set([
"To Kill a Mockingbird",
"1984",
"Foundation"
])
// These are all different ways to create an empty array with a type annotation.
var soda4: [String] = ["Coke", "Pepsi", "Irn-Bru"]
var teams4: [String] = [String]()
var cities4: [String] = []
var clues4 = [String]()
// Type annotation can also be useful when a variable is declared without an initial value.
let username4: String
// some complex logic
username4 = "@blackpanther"
// some more logic
print(username4)
/*
My Checkpoint 2
Create an array of strings, then write some code that prints the number of
items in the array and also the number of unique items in the array.
*/
let checkPoint2 = [
"apple",
"banana",
"cherry",
"apple",
"banana"
]
// Print the number of items in the array (total count)
print("Total number of items in the array: \(checkPoint2.count)")
// Create a Set from the array to get unique items
let uniqueCheckPoint2 = Set(checkPoint2)
// Print the number of unique items
print("Number of unique items: \(uniqueCheckPoint2.count)")
/*
--- Recap of Swift Arrays ---
Summary of what I've learned:
- **Arrays**: Ordered collections of values of the same type, accessed by an integer index.
- **Dictionaries**: Unordered collections of key-value pairs, where you access values using a specific key.
- **Sets**: Unordered collections of unique values, optimized for checking if an item is present.
- **Enums**: Custom data types with a fixed set of related values, useful for preventing invalid states.
- **Type Inference**: Swift automatically determines the data type of a variable. This is the default behavior.
- **Type Annotation**: Explicitly telling Swift what data type a variable should be. This is useful for clarity, for creating empty collections, or when a variable is declared without an initial value.
*/