BS
BleepingSwift
Published on

> Previewing Partially Generated Content with Foundation Models in SwiftUI

Authors
  • avatar
    Name
    Mick MacCallum
    Twitter
    @0x7fs

Apple's Foundation Models framework introduced at WWDC 2025 makes it straightforward to generate structured Swift data from on-device LLMs. When you stream responses using guided generation, the framework doesn't give you raw tokens—instead, it sends snapshots of your data structure with properties that fill in progressively. This is great for building responsive UIs, but it creates a challenge: how do you test those intermediate loading states in Xcode Previews without actually calling the model?

Previews need to be fast and deterministic. You can't wait for a real model response every time you tweak your layout. The solution is to create mock partially generated data that mimics what the streaming API produces.

Understanding PartiallyGenerated Types

When you mark a struct with @Generable, the macro generates a companion PartiallyGenerated type. This type mirrors your struct but makes all properties optional, allowing the framework to represent incomplete data as it streams in.

import FoundationModels

@Generable
struct MovieReview {
    @Guide(description: "The movie title being reviewed")
    let title: String

    @Guide(description: "A brief summary of the review")
    let summary: String

    @Guide(description: "Rating from 1 to 5", .range(1...5))
    let rating: Int

    @Guide(description: "Key points about the film")
    let highlights: [String]
}

When streaming, you receive MovieReview.PartiallyGenerated instances where title might have a value while summary, rating, and highlights are still nil. Your UI needs to handle these intermediate states gracefully.

Converting Complete Data to Partial States

The simplest approach for preview testing is starting with complete data and converting it to a partial form. The @Generable macro provides an asPartiallyGenerated() method for this:

extension MovieReview {
    static let sampleComplete = MovieReview(
        title: "Dune: Part Two",
        summary: "A stunning continuation of the epic saga with breathtaking visuals.",
        rating: 5,
        highlights: ["Incredible cinematography", "Strong performances", "Faithful adaptation"]
    )

    static var samplePartial: PartiallyGenerated {
        sampleComplete.asPartiallyGenerated()
    }
}

This gives you a fully populated partial instance, which is useful for testing the "completed" state of your streaming UI. But you also need to test what the view looks like when only some properties have arrived.

Creating Incomplete Partial States

To simulate mid-stream states, you can manually construct PartiallyGenerated instances. The generated type has an initializer that accepts optional values for each property:

extension MovieReview.PartiallyGenerated {
    static let titleOnly = MovieReview.PartiallyGenerated(
        title: "Dune: Part Two",
        summary: nil,
        rating: nil,
        highlights: nil
    )

    static let withSummary = MovieReview.PartiallyGenerated(
        title: "Dune: Part Two",
        summary: "A stunning continuation of the epic saga with breathtaking visuals.",
        rating: nil,
        highlights: nil
    )

    static let almostComplete = MovieReview.PartiallyGenerated(
        title: "Dune: Part Two",
        summary: "A stunning continuation of the epic saga with breathtaking visuals.",
        rating: 5,
        highlights: ["Incredible cinematography"]
    )
}

Now you can preview each stage of content arrival:

#Preview("Title Only") {
    ReviewCard(review: .titleOnly)
}

#Preview("With Summary") {
    ReviewCard(review: .withSummary)
}

#Preview("Almost Complete") {
    ReviewCard(review: .almostComplete)
}

Building a View That Handles Partial Data

Your view needs to gracefully handle missing properties. A common pattern is showing placeholder content or skeleton shapes for properties that haven't arrived yet:

struct ReviewCard: View {
    let review: MovieReview.PartiallyGenerated

    var body: some View {
        VStack(alignment: .leading, spacing: 12) {
            if let title = review.title {
                Text(title)
                    .font(.title2)
                    .fontWeight(.bold)
            } else {
                RoundedRectangle(cornerRadius: 4)
                    .fill(Color.gray.opacity(0.3))
                    .frame(height: 24)
            }

            if let summary = review.summary {
                Text(summary)
                    .foregroundStyle(.secondary)
            } else {
                VStack(spacing: 6) {
                    ForEach(0..<2, id: \.self) { _ in
                        RoundedRectangle(cornerRadius: 4)
                            .fill(Color.gray.opacity(0.3))
                            .frame(height: 16)
                    }
                }
            }

            if let rating = review.rating {
                HStack(spacing: 4) {
                    ForEach(1...5, id: \.self) { star in
                        Image(systemName: star <= rating ? "star.fill" : "star")
                            .foregroundStyle(star <= rating ? .yellow : .gray)
                    }
                }
            }

            if let highlights = review.highlights, !highlights.isEmpty {
                ForEach(highlights, id: \.self) { highlight in
                    Label(highlight, systemImage: "checkmark.circle.fill")
                        .foregroundStyle(.green)
                }
            }
        }
        .padding()
        .background(.regularMaterial)
        .clipShape(RoundedRectangle(cornerRadius: 12))
    }
}

Simulating Streaming in Previews

Static previews show individual states, but sometimes you want to see the animation of content appearing. You can create a mock async stream that emits progressively complete data:

extension MovieReview {
    static func mockStream(delay: Duration = .milliseconds(500)) -> AsyncThrowingStream<PartiallyGenerated, Error> {
        AsyncThrowingStream { continuation in
            Task {
                let states: [PartiallyGenerated] = [
                    .titleOnly,
                    .withSummary,
                    .almostComplete,
                    sampleComplete.asPartiallyGenerated()
                ]

                for state in states {
                    try await Task.sleep(for: delay)
                    continuation.yield(state)
                }
                continuation.finish()
            }
        }
    }
}

Then create a preview wrapper that consumes the stream:

struct StreamingPreviewWrapper: View {
    @State private var review: MovieReview.PartiallyGenerated?

    var body: some View {
        Group {
            if let review {
                ReviewCard(review: review)
            } else {
                ProgressView()
            }
        }
        .task {
            do {
                for try await partial in MovieReview.mockStream() {
                    withAnimation(.easeInOut(duration: 0.2)) {
                        review = partial
                    }
                }
            } catch {
                // Handle error in production
            }
        }
    }
}

#Preview("Streaming Animation") {
    StreamingPreviewWrapper()
        .padding()
}

This lets you validate that transitions between states animate smoothly and that your skeleton placeholders transform into real content naturally.

Parsing Incomplete JSON for Edge Cases

The Foundation Models framework parses JSON internally, and it handles incomplete fragments gracefully. If you need to test very specific partial states or want to validate parsing behavior, you can create GeneratedContent from raw JSON strings:

extension MovieReview.PartiallyGenerated {
    static func fromJSON(_ json: String) throws -> Self {
        let data = Data(json.utf8)
        return try JSONDecoder().decode(Self.self, from: data)
    }
}

// Test with a truncated response
let incompleteJSON = """
{"title": "Dune: Part Two", "summary": "A stunning"}
"""

#Preview("Mid-Summary") {
    if let partial = try? MovieReview.PartiallyGenerated.fromJSON(incompleteJSON) {
        ReviewCard(review: partial)
    }
}

This approach is particularly useful when debugging edge cases where properties are partially populated or when arrays have been started but not completed.

Keeping Previews Fast

The whole point of these techniques is avoiding real model calls in previews. A few practices help maintain fast iteration:

Keep your mock data definitions in extensions marked with #if DEBUG if you're concerned about shipping extra code. Create a small library of representative partial states that cover your main UI scenarios. For animated previews, use short delays (200-500ms) that are long enough to see the animation but short enough not to waste time.

When you do need to test against the real model, do it in the simulator or on device rather than in previews. Previews are for rapid visual iteration—save the integration testing for a proper test run.

The ability to preview every state of a streaming UI without waiting for actual AI generation makes it practical to polish those loading animations and ensure your app feels responsive even while content is still arriving.

subscribe.sh

// Stay Updated

Get notified when I publish new tutorials on Swift, SwiftUI, and iOS development. No spam, unsubscribe anytime.

>

By subscribing, you agree to our Privacy Policy.