Have you ever opened a SwiftUI file, waited 30 seconds for the canvas to load, only to watch it crash because it tried to fetch live data from a network API?
Hitting real network layers inside your Xcode Previews makes them sluggish, flaky, and frustrating.
The fix is simple: use an extension-based static mock provider. This decouples your preview layer from live networks, making your canvas render instantly.
When you pass a live service or API client directly into your view model, Xcode Previews attempt to run real network requests every time the canvas refreshes.
// ❌ THE SLUGGISH WAY
struct ProfileView: View {
@StateObject private var viewModel = ProfileViewModel(service: LiveUserService())
var body: some View {
Text(viewModel.user?.name ?? "Loading...")
.task { await viewModel.fetchProfile() } // Hits real API!
}
}
#Preview {
ProfileView() // Takes ages to load or fails offline
}To fix this, abstract your data fetching behind a protocol. Then, create a static mock extension that supplies instant, hardcoded data exclusively for your previews.
protocol UserServiceProtocol {
func fetchUserData() async throws -> User
}Extend your protocol with a static property containing mock data. This lives cleanly in memory and requires zero network overhead.
extension UserServiceProtocol where Self == MockUserService {
static var mockSuccess: MockUserService {
MockUserService(mockUser: User(name: "Jane Doe", email: "jane@example.com"))
}
static var mockFailure: MockUserService {
MockUserService(shouldFail: true)
}
}
// Simple mock implementation
struct MockUserService: UserServiceProtocol {
let mockUser: User?
var shouldFail = false
init(mockUser: User? = nil, shouldFail: Bool = false) {
self.mockUser = mockUser
self.shouldFail = shouldFail
}
func fetchUserData() async throws -> User {
if shouldFail { throw URLError(.badServerResponse) }
return mockUser ?? User(name: "Test", email: "test@test.com")
}
}Now, update your view model to accept the protocol, and inject your static mock directly into your Xcode Preview block.
// ✅ THE INSTANT WAY
class ProfileViewModel: ObservableObject {
@Published var user: User?
private let service: UserServiceProtocol
init(service: UserServiceProtocol) {
self.service = service
}
func fetchProfile() async {
self.user = try? await service.fetchUserData()
}
}
#Preview("Success State") {
// Renders instantly with mock data!
let viewModel = ProfileViewModel(service: .mockSuccess)
return ProfileView(viewModel: viewModel)
}
#Preview("Failure State") {
// Easily test your error UI without breaking anything
let viewModel = ProfileViewModel(service: .mockFailure)
return ProfileView(viewModel: viewModel)
}.mockSuccess and .mockFailure instantly to test how your UI handles errors.Waiting on Xcode Previews is a massive productivity killer. By shifting to a protocol-based mock architecture, you decouple your UI from unstable network environments and make your canvas render instantly.
As a bonus, this setup forces you to write cleaner, highly testable code that makes unit testing your view models a breeze later on.
Give this a try in your current project - your development loop (and your sanity) will thank you.
Thank you for reading. If you have any questions feel free to follow me on X and send me a DM. If this article helped you, Buy me a coffee.