Stop Crashing Your Swift App! The Right Way to Handle UI Updates

Manan Jain
3 min readJan 22, 2025

--

Photo by Nastia Petruk on Unsplash

Updating your app’s UI from a background thread might seem harmless, but it can lead to crashes, strange behavior, and frustrated users. In this guide, we’ll break down why updating UI elements from a background thread in Swift is illegal, how to do it the right way, and provide examples to help you avoid common pitfalls.

Why UI Updates Must Happen on the Main Thread

Swift’s UI frameworks — UIKit (iOS) and AppKit (macOS) — are not thread-safe, meaning they can’t handle multiple threads updating the UI simultaneously. If a background thread tries to modify a UI element, your app might behave unpredictably or crash outright.

Key Reasons to Always Use the Main Thread for UI:

  1. Thread Safety: UIKit and AppKit are designed to work on a single thread (the main thread). Running UI updates on a background thread leads to unpredictable results and crashes.
  2. Performance Issues: The main thread handles all UI interactions, animations, and screen rendering. Background thread updates can interfere with smooth performance.
  3. Race Conditions: When multiple threads try to update the UI at the same time, unexpected behaviors like flickering or incomplete updates can occur

Reference: Apple’s documentation on Thread Safety

What Happens If You Update UI on a Background Thread?

If you attempt to update UI elements from a background thread, you may face:

  • App crashes with messages like UIKit called from a background thread.
  • UI glitches where elements don’t update correctly.
  • Inconsistent behavior, especially when dealing with animations or layout updates.

Example of a Common Mistake (Wrong Way)

func fetchData() {
DispatchQueue.global().async {
// Fetching data from API
let data = "Hello from server"

// ❌ Updating UI on a background thread - this may cause a crash!
self.label.text = data
}
}

How to Fix It (Right Way)

To fix this, ensure you switch back to the main thread before updating the UI:

func fetchData() {
DispatchQueue.global().async {
// Fetching data from API
let data = "Hello from server"

DispatchQueue.main.async {
// ✅ Updating UI safely on the main thread
self.label.text = data
}
}
}

Reference: Concurrency Programming Guide by Apple

When Should You Use the Main vs. Background Thread?

TaskThread to UseUpdating UI (labels, views, animations)Main ThreadFetching data from an APIBackground ThreadProcessing large datasetsBackground ThreadHandling user inputMain ThreadFile I/O operationsBackground Thread

A simple rule to remember is: “Main for Show, Background for Work.”

Practical Example: Loading Data and Updating UI

Let’s look at a more complete example:

func loadUserData() {
DispatchQueue.global(qos: .background).async {
let userData = fetchUserDataFromServer()

DispatchQueue.main.async {
self.usernameLabel.text = userData.name
self.profileImageView.image = UIImage(named: userData.profilePicture)
}
}
}

In this example:

  • DispatchQueue.global(qos: .background) runs data-fetching in the background.
  • DispatchQueue.main.async updates the UI safely.

Conclusion

Updating UI on the main thread is a fundamental rule in Swift development that ensures a smooth and crash-free user experience. Always:

  • Offload heavy tasks to background threads.
  • Return to the main thread for UI updates.
  • Use tools like Main Thread Checker to catch mistakes early.

By following these best practices, you’ll avoid hard-to-debug issues and build a more responsive app.

--

--

Manan Jain
Manan Jain

Written by Manan Jain

iOS Developer || Python language enthusiast || Blogger

No responses yet