Chapter 13: Error Handling in Swift: Writing Robust and Reliable Code
This post is part of a series about programming with Swift to build iOS apps. Be sure to check out the other posts in the series for a complete journey into mastering iOS development.
In any app, things don’t always go as planned. Whether it’s failing to fetch data from a server, invalid user input, or unexpected conditions, errors are inevitable. That’s why error handling is a crucial skill for any developer. Swift provides powerful tools to detect, handle, and recover from errors gracefully.
In this chapter, we’ll explore:
The basics of error handling in Swift.
Defining and throwing errors.
Using
do-catch,try, andthrows.Best practices for handling errors effectively.
By the end, you’ll be able to write more robust and reliable Swift code.
1. What is Error Handling?
Error handling is the process of responding to and managing problems that occur during program execution. In Swift, errors are represented by values that conform to the Error protocol.
Common Error Scenarios
A network request fails.
A file cannot be found.
User input is invalid.
Swift’s error handling model uses do-catch blocks, throws functions, and the try keyword to manage these situations.
2. Defining and Throwing Errors
To define an error in Swift, create an enum that conforms to the Error protocol:
enum FileError: Error {
case fileNotFound
case unreadable
case unknown
}You can then use the throw keyword to indicate an error:
func readFile(named name: String) throws -> String {
if name.isEmpty {
throw FileError.fileNotFound
}
return "File content"
}3. Handling Errors with do-catch
To handle errors, use a do-catch block. This lets you try a function that can throw an error and handle any errors that occur:
do {
let content = try readFile(named: "example.txt")
print(content)
} catch FileError.fileNotFound {
print("The file was not found.")
} catch FileError.unreadable {
print("The file is unreadable.")
} catch {
print("An unknown error occurred: \(error).")
}do: The block where you try code that might throw an error.catch: Blocks to handle specific or general errors.
4. Propagating Errors with throws
Functions that can throw errors must be marked with the throws keyword. You can propagate errors up the call stack using try without handling them directly:
func processFile(named name: String) throws {
let content = try readFile(named: name)
print(content)
}To call a throwing function, use try within a do-catch block or mark the calling function with throws.
5. Using try? and try!
Swift provides shorthand options for handling errors when you don’t need detailed error management:
try?: Converts the result to an optional. If an error is thrown, the result isnil.
let content = try? readFile(named: "example.txt")
print(content ?? "No content available.")try!: Forces a try and crashes the app if an error is thrown. Use cautiously when you’re certain no errors will occur.
let content = try! readFile(named: "example.txt")
print(content)6. Best Practices for Error Handling
Catch Specific Errors: Handle errors as specifically as possible to avoid masking issues.
Avoid Overusing
try!: Usetry!sparingly, as it can lead to crashes if an error occurs.Provide Meaningful Error Messages: Use custom errors and include helpful context in your error descriptions.
Graceful Recovery: When possible, recover from errors instead of failing silently or crashing.
7. From Playground to SwiftUI
Let’s apply error handling in a SwiftUI example by creating a Chapter13View that simulates loading file content.
Step 1: Create Chapter13View.swift
Add a new Swift file named Chapter13View.swift to your project, and include the following code:
import SwiftUI
struct Chapter13View: View {
@State private var fileName: String = ""
@State private var fileContent: String = ""
@State private var errorMessage: String?
var body: some View {
VStack(spacing: 20) {
TextField("Enter file name", text: $fileName)
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding()
Button("Load File") {
do {
fileContent = try loadFile(named: fileName)
errorMessage = nil
} catch FileError.fileNotFound {
errorMessage = "The file was not found."
} catch {
errorMessage = "An unknown error occurred."
}
}
.padding()
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(8)
if let errorMessage = errorMessage {
Text(errorMessage)
.foregroundColor(.red)
} else {
Text(fileContent)
}
}
.padding()
}
func loadFile(named name: String) throws -> String {
if name.isEmpty {
throw FileError.fileNotFound
}
return "Content of \(name)"
}
}Step 2: Update ContentView.swift
Modify ContentView to load Chapter13View:
import SwiftUI
struct ContentView: View {
var body: some View {
Chapter13View()
}
}
#Preview {
ContentView()
}8. Challenges
Extend the
loadFilefunction to throw different errors, such as unreadable content or permission issues, and handle them inChapter13View.Add a
Retrybutton to reset the error state and try loading the file again.Use
try?to load a default file if the user input is invalid.
What’s Next?
With error handling in your toolkit, your apps can recover gracefully from unexpected situations. In the next chapter, we’ll explore Concurrency in Swift, learning how to handle asynchronous tasks efficiently using async/await.
Let’s keep building!

