Fix Sluggish SwiftUI Previews Instantly With Static Mock Data

August 29, 2026

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.


The Problem: Fetching Live Data in Previews

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
}

The Fix: Protocol-Based Mock Extensions

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.

Step 1: Create the Protocol

protocol UserServiceProtocol {
    func fetchUserData() async throws -> User
}

Step 2: Build the Static Mock Extension

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")
    }
}

Step 3: Inject the Mock into Previews

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)
}

Why This Works

  • Zero Network Requests: Your canvas renders in milliseconds.
  • Offline Development: Work on your UI on planes or trains without Wi-Fi.
  • State Testing: Toggle between .mockSuccess and .mockFailure instantly to test how your UI handles errors.

Wrap Up

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.