Swift
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Optionals
-
Section 4: Object-Oriented and Value Types
-
Section 5: Memory Management
-
Section 6: Generics and Error Handling
-
Section 7: Concurrency
-
Section 8: Working with Collections
-
Section 9: Codable and Data Handling
-
Section 10: Protocol-Oriented Programming
-
Section 11: Testing and Tooling
-
Section 12: Practical Projects
-
Section 13: Interview Practice
-
Section 14: More Practice Exercises
-
Section 15: More Standard Library
-
Section 16: Advanced Concurrency
-
Section 17: Foundation Framework Deep Dive
-
Section 18: URLSession and Networking Deep Dive
-
Section 19: Combine Framework
-
Section 20: SwiftUI Fundamentals for Swift Developers
-
Section 21: Server-Side Swift with Vapor
-
Section 22: Swift Package Manager Deep Dive
-
Section 23: Swift Concurrency Deep Dive
-
Section 24: More Language Features
-
Section 25: Error Handling Deep Dive
-
Section 26: Testing Deep Dive
-
Section 27: Data Structures and Algorithms in Swift
-
Section 28: More Practice Exercises
-
Section 29: More Interview Practice
-
Section 30: Swift Macros (Swift 5.9+)
-
Section 31: Property Wrappers Ecosystem
-
Section 32: Swift Interop Deep Dive
-
Section 33: iOS App Architecture Patterns
-
Section 34: Performance and Debugging
-
Section 35: App Distribution and CI/CD
-
Section 36: More Practical Projects
-
Section 37: SwiftData and Persistence
-
Section 38: More Design Patterns
-
Section 39: More Review and Practice
-
Section 40: More Foundation Deep Dive
-
Section 41: Advanced Collections in Swift
-
Section 42: Advanced Generics Practice
-
Section 43: UIKit for Legacy and Hybrid Apps
-
Section 44: watchOS and visionOS Development Basics
-
Section 45: More Networking Patterns
-
Section 46: More Testing Practice
-
Section 47: Accessibility in Swift Apps
-
Section 48: Localization
-
Section 49: More Practical Projects Round 2
-
Section 50: Swift Charts Framework
-
Section 51: More Interview and Algorithm Practice
-
Section 52: Final Practice and Mastery
-
Section 53: Swift Compiler and Build System
-
Section 54: More Concurrency Practice
-
Section 55: App Store Guidelines and Review
-
Section 56: More Design and Architecture
74: UserDefaults for Simple Persistence
You've probably had that annoying experience where you close an app, reopen it, and find that it's completely forgotten who you are or how you liked your settings configured. For small pieces of data—like a username, a "Dark Mode" toggle, or a volume preference—you don't need a full-blown database. That's where UserDefaults comes in.
Think of UserDefaults as a simple key-value store. It's essentially a dictionary that the system saves to the disk for you. It isn't meant for large datasets, but for "preferences," it's the gold standard.
Saving a basic user profile
Let's build a tiny settings manager for a hypothetical reading app. I want to save the user's nickname so we can greet them when they return. In Swift, we access the shared defaults object through UserDefaults.standard.
let defaults = UserDefaults.standard
defaults.set("SwiftLearner", forKey: "username")
That's it. One line. The system takes that string and writes it to a plist file in the app's sandbox. I usually avoid creating a local variable for UserDefaults.standard if I'm only doing one thing, but since we're doing a few, it keeps the code cleaner.
Handling a theme toggle
Next, let's add a boolean to track whether the user prefers Dark Mode. Booleans are handled exactly the same way as strings.
defaults.set(true, forKey: "darkModeEnabled")
Now we have two pieces of data stored. But here is where things usually go sideways in a real project. Let me show you a mistake I actually made in a production app a few years ago.
The "Magic String" trap
I decided to read the theme preference back to apply the colors to my UI. I wrote this:
let isDark = defaults.bool(forKey: "dark_mode_enabled")
print("Is dark mode on? \(isDark)")
// Output: Is dark mode on? false
Wait. I just set it to true. Why is it returning false? If you look closely, I used "darkModeEnabled" (camelCase) when saving, but "dark_mode_enabled" (snake_case) when reading. UserDefaults doesn't throw an error when you provide a key that doesn't exist; it just returns the default value for that type (which is false for Booleans and nil for Strings).
To stop this from happening, I never use "magic strings" directly in the set or bool methods. Instead, I'll use a private enum or a struct to manage my keys.
enum UserKey {
static let username = "username"
static let darkMode = "darkModeEnabled"
}
// Now it's impossible to have a typo mismatch
defaults.set(true, forKey: UserKey.darkMode)
let isDark = defaults.bool(forKey: UserKey.darkMode)
Retrieving the data on launch
Finally, we need to actually use these values when the app starts. Since string(forKey:) returns an optional (because the user might not have set a name yet), I'll use the nil-coalescing operator to provide a friendly fallback.
let savedName = UserDefaults.standard.string(forKey: UserKey.username) ?? "Guest"
let isDarkMode = UserDefaults.standard.bool(forKey: UserKey.darkMode)
print("Welcome back, \(savedName)!")
print("Applying \(isDarkMode ? "Dark" : "Light") theme.")
It's simple, it's fast, and it survives an app restart. Just remember: if you start finding yourself saving giant arrays of complex objects here, stop. That's when you move to File Storage or Core Data.
📋 Practical Task
Build a Persistent High Score Tracker
Create a small program that manages a player's high score for a game. Your code should do the following:
- Define a
ScoreKeystruct or enum to avoid string typos. - Write a function
saveHighScore(score: Int)that saves an integer toUserDefaults. - Write a function
getHighScore() -> Intthat retrieves the score. - In your main execution, save a score of
5000, then print it back to the console to verify it was stored correctly.
There are no comments for now.