-
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
231: Practice Exercise: Building a Type-Safe Deep Linking System
Deep links are one of those features that seem simple until you have twenty of them and a handful of developers trying to maintain them. If you're just passing strings around your app like "profile_page" or "settings/notifications", you're essentially playing a game of "I hope I didn't typo this" every time you add a new screen. I've spent too many hours debugging a broken link because someone wrote "userProfile" in one file and "user_profile" in another.
The goal here is to move the "stringiness" of a URL to a single entry point and immediately convert it into a type-safe Swift enum. That way, the rest of your app doesn't even know URLs exist; it just knows how to handle a DeepLink case.
Defining our destinations
Let's imagine we're building a book-selling app. We need to handle links to specific books, category pages, and a search page. Instead of using a dictionary or a bunch of constants, I'm going to use an enum with associated values. This lets us bundle the necessary ID or query string directly with the route.
enum DeepLink {
case book(id: String)
case category(name: String)
case search(query: String)
}
Now, we need a way to turn a URL into one of these cases. I like to keep this logic in a dedicated parser. It keeps the AppDelegate or SceneDelegate clean.
Turning URLs into types
I'll create a DeepLinkParser. I want this to be a simple utility that takes a URL and returns an optional DeepLink. If the URL is malformed or doesn't match our schema, we just return nil and let the app handle it (usually by just ignoring it or showing a 404 page).
struct DeepLinkParser {
static func parse(url: URL) -> DeepLink? {
guard url.scheme == "bookstore" else { return nil }
let pathComponents = url.pathComponents.filter { $0 != "/" }
guard let route = pathComponents.first else { return nil }
switch route {
case "book":
// I'll grab the ID from the second component
let bookId = pathComponents[1]
return .book(id: bookId)
case "category":
let categoryName = pathComponents[1]
return .category(name: categoryName)
case "search":
// Search usually uses query parameters: bookstore://search?q=swift
let components = URLComponents(url: url, resolvingAgainstBaseURL: false)
let query = components?.queryItems?.first(where: { $0.name == "q" })?.value
return query.map { .search(query: $0) }
default:
return nil
}
}
}
Fixing a potential crash
Wait. Look at that book and category logic I just wrote: pathComponents[1]. I just did the exact thing I told you to avoid. If someone sends a link like bookstore://book without an actual ID, the app is going to crash with an "Index out of range" error. I've actually done this in production before—don't be like me.
Let's fix that by safely checking the count of the components before we try to access them. I'll refine the switch statement to ensure we actually have the data we need.
switch route {
case "book":
guard pathComponents.count > 1 else { return nil }
return .book(id: pathComponents[1])
case "category":
guard pathComponents.count > 1 else { return nil }
return .category(name: pathComponents[1])
case "search":
let components = URLComponents(url: url, resolvingAgainstBaseURL: false)
let query = components?.queryItems?.first(where: { $0.name == "q" })?.value
return query.map { .search(query: $0) }
default:
return nil
}
Wiring it to the UI
Now that we have a type-safe enum, the actual navigation becomes trivial. You can just switch over the DeepLink object. Because it's an enum, the compiler will force you to handle every case, meaning you'll never forget to implement a route.
func handleDeepLink(_ link: DeepLink) {
switch link {
case .book(let id):
print("Navigating to book details for ID: \(id)")
// coordinator.showBook(id: id)
case .category(let name):
print("Opening category: \(name)")
// coordinator.showCategory(name: name)
case .search(let query):
print("Performing search for: \(query)")
// coordinator.showSearch(query: query)
}
}
This pattern is a lifesaver. You've isolated the "unsafe" part of your app (parsing raw strings from the outside world) to one single function. Once the data passes through that gate, it's fully typed and safe to use throughout your entire navigation stack.
📋 Practical Task
Exercise: Adding a User Profile Route
You need to extend the BookStore deep linking system to support user profiles. Users should be able to click a link like bookstore://profile/username123 to view another user's profile.
Your Task:
- Update the
DeepLinkenum to include aprofilecase that takes ausername: String. - Modify the
DeepLinkParser.parse(url:)method to handle the"profile"route. Ensure you implement the safety check to avoid index-out-of-range crashes if the username is missing. - Update the
handleDeepLink(_:)function to include a print statement confirming that the app is navigating to the profile of the specific user.
There are no comments for now.