Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create #Preview with async code in swiftUI?

I would like to be able to create a #Preview that uses some values that are computed with async/await.

Below is a sample code with the first 2 previews working fine. The last one calling an async func fails.

import SwiftUI
import SwiftData

struct TestUIView: View {
    
    var passedValue:String
    
    var body: some View {
        Text("passedValue: \(passedValue)")
    }
}


#Preview {
    let passedValue="My value"
    return TestUIView(passedValue: passedValue)
}

#Preview {
    let passedValue=MyClass.getValue()
    return TestUIView(passedValue: passedValue)
}

#Preview {
    var passedValue:String {
        get async {
            return await MyClass.getValueAsync()
        }
    }
    return TestUIView(passedValue: passedValue)
}



class MyClass {
    
    public class func getValue() -> String {
        return "from getValue"
    }
    
    public class func getValueAsync() async -> String {
        return "from getValueAsync"
    }
}

The Preview error is: Compiling failed: 'async' property access in a function that does not support concurrency

In the real app, the data passed on is created in async mode and once available passed on to the next View. I want to be able to use the same function that creates this data in the Preview, rather then creating some dummy data only for the #Preview.

like image 932
matyasl Avatar asked Mar 27 '26 02:03

matyasl


2 Answers

The way you are going to solve this Preview Macro problem is the same way you would solve other issues such as using @State variables: create a view struct within the macro.

#Preview("from getValueAsync") {
    
    struct AsyncTestView: View {
        
        @State var passedValue: String = ""
        
        var body: some View {
            TestUIView(passedValue: passedValue)
                .task {
                    passedValue = await MyClass.getValueAsync()
                }
        }
    }
    
    return AsyncTestView()
}

You can also create it outside of your preview, but then archiving would not strip the code when creating a release. Another thing, If you are using multiple macros, it helps to name them.

like image 110
Yrb Avatar answered Mar 28 '26 14:03

Yrb


A bit more generic approach, to avoid creating boilerplate supplementary structs every time.

#Preview {
    AsyncModel { asyncValue in
        TestUIView(passedValue: asyncValue)
    } model: {
        await MyClass.getValueAsync()
    }
}

It's powered by small generic struct that also supports error handling.

struct AsyncModel<VisualContent: View, ModelData>: View {
    // Standard view builder, accepting async-fetched data as a parameter
    var viewBuilder: (ModelData) -> VisualContent
    // data fetcher. Notice it can throw as well
    var model: () async throws -> ModelData?
    
    @State private var modelData: ModelData?
    @State private var error: Error?
    
    var body: some View {
        safeView
            .task {
                do {
                    self.modelData = try await model()
                } catch {
                    self.error = error
                    // print detailed error info to console
                    print(error)
                }
            }
    }
    
    @ViewBuilder
    private var safeView: some View {
        if let modelData {
            viewBuilder(modelData)
        }
        // in case of error, its description rendered 
        // right on preview to make troubleshooting faster
        else if let error {
            Text(error.localizedDescription)
                .foregroundStyle(Color.red)
        }
        // a stub for awaiting.
        // Actually, we should return some non-empty view from here
        // to make sure .task { } is triggered
        else {
            Text("Calculating async data...")
        }
    }
}
like image 44
Cemen Avatar answered Mar 28 '26 14:03

Cemen



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!