For an iOS developer who has spent years struggling with Storyboards, XIBs, and the infinite loop of “Build & Run”, the arrival of SwiftUI was a revolution. But the real jewel in the crown isn’t just the declarative syntax, but the ability to see changes in real-time.
Knowing how to show SwiftUI previews in Xcode is not just a visual trick; it’s a development methodology that exponentially accelerates your workflow. You no longer need to wait for the simulator to boot up to see if that button has the correct padding.
In this tutorial, we will break down everything you need to know about the Previews system in Xcode. From the new #Preview macro introduced in Swift 5.9, to complex data injection and cross-platform development for macOS and watchOS. If you want to take your Swift programming to the next level, this is your definitive manual.
The Evolution: From PreviewProvider to the #Preview Macro
Before Xcode 15, developers had to write a helper structure that conformed to the PreviewProvider protocol. While it worked, it was boilerplate code that cluttered your files.
With the arrival of Xcode 15 and Swift 5.9, Apple simplified this drastically. We now use a Macro. A macro in Swift transforms code at compile time, allowing for much cleaner syntax.
To show a basic preview, you simply add this to the end of your Swift file:
import SwiftUI
struct ContentView: View {
var body: some View {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundStyle(.tint)
Text("Hello, iOS Developer!")
}
.padding()
}
}
// The new standard way to show the preview in Xcode 15+
#Preview {
ContentView()
}
If you still maintain old code or need to support older versions of Xcode for some business reason, you might encounter the old syntax. It is important to recognize it:
// Legacy Way (Xcode 14 and below)
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Enabling the Canvas in Xcode
Sometimes, you open Xcode and the preview is nowhere to be found. To show SwiftUI previews in Xcode, you need to ensure the “Canvas” is active.
- Go to the top menu: Editor.
- Select Canvas.
- Alternatively, use the master keyboard shortcut:
Cmd + Option + Enter.
Once opened, you will see that the Canvas has two main modes in the bottom left corner:
- Live Mode (Play): The view is interactive. You can scroll, press buttons, and navigate. It’s like having a mini-simulator.
- Selectable Mode (Arrow): Allows you to click on visual elements in the Canvas and Xcode will highlight the corresponding code in the editor (and vice versa). Ideal for debugging complex UI.
Customizing Previews with Traits
The real power of modern Swift programming lies in testing multiple scenarios without running the app. What does your view look like in dark mode? What about in landscape? And with extra-large dynamic text?
The #Preview macro accepts parameters (Traits) that configure the preview environment instantly.
1. Orientation and Names
You can name your previews to distinguish them in the Canvas and force landscape orientation.
#Preview("Horizontal Screen", traits: .landscapeLeft) {
ContentView()
}
2. Multiple Previews in the same file
For an iOS developer, it is vital to see edge cases. You can stack multiple #Preview macros to see simultaneous variations.
#Preview("Light Mode") {
ContentView()
.preferredColorScheme(.light)
}
#Preview("Dark Mode") {
ContentView()
.preferredColorScheme(.dark)
}
#Preview("Giant Text") {
ContentView()
.environment(\.dynamicTypeSize, .accessibility5)
}
By doing this, the Xcode Canvas will split the screen and show you all three versions at once. This saves hours of manual testing.
Data and Dependency Injection
The number one challenge when showing SwiftUI previews in Xcode is when your view depends on external data. If your view expects a User object or a ViewModel, the preview will crash if you don’t provide them.
In SwiftUI, we must get used to creating static “Mock Data”.
Handling @Binding
If your view has a @Binding var text: String, you cannot pass it a literal String. You need a constant binding.
struct SearchBar: View {
@Binding var text: String
var body: some View {
TextField("Search...", text: $text)
.textFieldStyle(.roundedBorder)
.padding()
}
}
#Preview {
// .constant creates a fake immutable binding for the preview
SearchBar(text: .constant("SwiftUI Rocks"))
}
Handling @Environment and Models
For complex views using SwiftData or CoreData, or a complex ViewModel, the best practice in Swift programming is to create an extension of your model with sample data.
class UserViewModel: ObservableObject {
@Published var name: String
@Published var isPremium: Bool
init(name: String, isPremium: Bool) {
self.name = name
self.isPremium = isPremium
}
// Static property to use in previews
static let mockUser = UserViewModel(name: "Steve Jobs", isPremium: true)
}
struct ProfileView: View {
@StateObject var viewModel: UserViewModel
var body: some View {
Text(viewModel.name)
.foregroundStyle(viewModel.isPremium ? .yellow : .black)
}
}
#Preview {
ProfileView(viewModel: UserViewModel.mockUser)
}
Cross-Platform Development: iOS, macOS, and watchOS
SwiftUI promises “Learn once, apply everywhere.” However, screens are very different. Xcode allows you to preview different devices without changing the active simulator in the top toolbar.
Although the #Preview macro tries to infer the device, sometimes we want to force an iPhone SE view in a project targeting iPhone 15 Pro, or see the iPad version.
Unfortunately, with the new macro, Apple has removed the .previewDevice("iPhone SE") modifier we used before in favor of selecting it directly in the Canvas. At the bottom of the Canvas, there is a device icon where you can change the preview’s “Run Destination” instantly.
Tip for macOS
If you are developing for macOS within a multi-platform package, ensure the target selected in Xcode is “My Mac”. The SwiftUI Canvas uses the active target’s engine. If you have an Apple Watch selected, the Canvas will try to render the macOS view in a Watch environment, which will fail or look incorrect.
Troubleshooting: When the Preview Fails
It’s not all sunshine and rainbows. You will often see the dreaded message: “Automatic preview updating paused” or simply a crash. Here is a checklist for the distressed iOS developer:
- The “Diagnostics” button: When the preview fails, a “Diagnostics” button appears. Press it. Often the error isn’t with the preview, but with your normal Swift code preventing compilation.
- Dependencies in Init: If your view makes a network call in
init(), the preview will try to execute it. Since the preview runs in an isolated environment (sandbox), this can fail or be slow. Move network calls to.taskor.onAppear. - Clean Build Folder: The classic
Cmd + Shift + K. Sometimes the Canvas cache gets corrupted. Cleaning and rebuilding usually fixes it. - CoreData/SwiftData Crashes: If you use
@Environment(\.modelContext)and don’t inject the container in the preview (.modelContainer(for: Item.self, inMemory: true)), the app will crash immediately.
// Example of SwiftData injection in Preview
import SwiftData
#Preview {
MainListView()
.modelContainer(for: Item.self, inMemory: true) // Vital: inMemory for speed
}
Conclusion: The Competitive Advantage
Mastering the art of showing SwiftUI previews in Xcode transforms your daily life. You stop guessing and start sculpting interfaces. The ability to iterate on visual design in seconds, test data variations, and check accessibility without running the full app is what defines a modern senior iOS developer.
Next time you open Xcode, don’t limit yourself to writing code blindly. Open the Canvas, configure your #Preview macros, and let SwiftUI show you what you are creating in real-time.
If you have any questions about this article, please contact me and I will be happy to help you 🙂. You can contact me on my X profile or on my Instagram profile.