swiftuiobservablepreview

In SwiftUI #Preview is crashing when using @Environment property wrapper (Observable Macro)


I am using the Observable macro and when I use @Environment property wrapper to instance my model the preview stop working. Sample code below

import SwiftUI
import Observation

@Observable class Library {
    // ...
}

Now in my main app I created an instance of Library and add that instance to the environment

@main
struct BookReaderApp: App {
    @State private var library = Library()


    var body: some Scene {
        WindowGroup {
            LibraryView()
                .environment(library)
        }
    }
}

Now if I want to retrieve the Library instance from any view using the @Environment property wrapper the preview stop working completely (Assume the BoolView actually exist)

struct LibraryView: View {
    @Environment(Library.self) private var library
    
    var body: some View {
        List(library.books) { book in
            BookView(book: book)
        }
    }
}

#Preview {
    LibraryView()
}

If I go back and don't use the Observable macro and just use the ObservableObject with @StateObject, .environemntObject and @EnvironmentObject the #Preview work fine.

This Observable macro is "related" new. Any idea why this is happening? Is there any workaround? I am working with Xcode Version 15.2. Thanks in advance for any kind of help!


Solution

  • When you declare an observable class using @Environment(Type.self)..., you are implicitly telling the view that the environment object will be present. However, in your preview you're not providing one, and the crash happens when the view tries to access the non-existent object.

    There's no need to create a separate EnvironmentKey, as this goes against the ethos of the Observable environment feature. All you need to do is provide an environment copy in your preview.

    The following would be sufficient:

    #Preview {
        LibraryView()
            .environment(Library())
    }
    

    However, it might be worth creating a dummy example object with values that would demonstrate your view's capabilities, e.g.:

    extension Library {
      static var example: Library = Library(books: [/* ... */])
    }
    
    #Preview {
      LibraryView()
        .environment(Library.example)
    }