如何使用带有示例数据的预览来查看使用任务修改器或模型进行 API 调用的视图?

问题描述 投票:0回答:1

我的应用程序进行 API 调用来获取大量数据,如果是简单的少量数据,则可以使用

.task
修饰符,或者使用模型。令我有点惊讶的是,预览实际上运行了获取数据的代码。但是,许多 API 调用需要身份验证和要调用的服务器,在存储库中进行身份验证(即使它是测试帐户)似乎不是一个好的做法,并且将预览绑定到(测试或演示)服务器看起来也不太好。如何在获取数据的视图的预览中使用示例数据?

struct ListView: View {
    @State private var books: [Book]

    var body: some View {
        List(books) { book in
            Text(book.title)
        }
        .task {
            books = await HTTPClient.fetchBooks()
        }
    }
}
struct ListView: View {
    @State private var model = BookModel()

    var body: some View {
        List(model.books) { book in
            Text(book.title)
        }
        .task {
            await model.fetchBooks()
        }
    }
}
ios swift swiftui
1个回答
0
投票

您应该创建一个像这样的协议:

protocol HTTPClient {
    static func fetchBooks() async throws -> [Book]
}

并且该协议有两个实现:

enum RealHTTPClient: HTTPClient {
    static func fetchBooks() async throws -> [Book] {
        // ... do actual work here!
    }
}

enum MockHTTPClient: HTTPClient {
    static func fetchBooks() -> [Book] {
        // return fake data here
        [Book(title: "Book 1"), Book(title: "Book 2"), Book(title: "Book 3")]
    }
}

然后,将其注入

Environment

extension EnvironmentValues {
    // using the @Entry macro in iOS 18 for convenience
    // you don't have to use @Entry. Just do it the old way if you are on a lower version
    @Entry var httpClient: any HTTPClient.Type = MockHTTPClient.self
}

这里我将

MockHTTPClient
设置为默认值。这使得使用真正的客户选择加入。另外,我在这里使用元类型和
static
方法来避免并发问题。如果您需要 HTTP 客户端具有状态,则需要确保它们不会跨并发上下文传递,但这超出了本问题的范围。

在您看来,您可以这样做:

struct ContentView: View {
    @State private var books: [Book] = []
    @Environment(\.httpClient) var httpClient

    var body: some View {
        List(books) { book in
            Text(book.title)
        }
        .task {
            do {
                books = try await httpClient.fetchBooks()
            } catch {
                print(error)
            }
        }
    }
}

#Preview {
    ContentView() // this will not call the real server because the default value of httpClient is MockHTTPClient.self
}

在根视图,你可以注入真正的HTTP客户端。

WindowGroup {
    RootView()
}
.environment(\.httpClient, RealHTTPClient.self)

如果你想在

fetchBooks
中有一个
BookModel
方法,它应该以
HTTPClient.Type
作为参数:

@Observable
class BookModel {
    var books = [Book]()
    
    // don't worry about this @MainActor - nonisolated async operations
    // you do in the body of fetchBooks is still running off of the MainActor
    @MainActor
    func fetchBooks(with httpClient: any HTTPClient.Type) async throws {
        // as an example
        books = try await httpClient.fetch(url: "https://example.com/books")
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.