构建 SwiftUI 应用程序的网络层的行之有效的方法是什么?具体来说,您如何使用 URLSession 来构建要在 SwiftUI View 中显示的 JSON 数据并处理从 iOS 13.4 开始可以正确发生的所有不同状态?
最佳答案
这是我在上一个项目中想到的:
View#onReceive
直接在 SwiftUI 中订阅发布者,但将发布者封装在模型类中总体上看起来更干净) .onAppear
如果尚未加载,则触发加载的修饰符。 .overlay
修饰符可以方便地根据状态显示进度/错误 View 该方法的独立示例代码(也可在我的 SwiftUIPlayground 中找到):
// SwiftUIPlayground
// https://github.com/ralfebert/SwiftUIPlayground/
import Combine
import SwiftUI
struct TypiTodo: Codable, Identifiable {
var id: Int
var title: String
}
class TodosModel: ObservableObject {
@Published var todos = [TypiTodo]()
@Published var state = State.ready
enum State {
case ready
case loading(Cancellable)
case loaded
case error(Error)
}
let url = URL(string: "https://jsonplaceholder.typicode.com/todos/")!
let urlSession = URLSession.shared
var dataTask: AnyPublisher<[TypiTodo], Error> {
self.urlSession
.dataTaskPublisher(for: self.url)
.map { $0.data }
.decode(type: [TypiTodo].self, decoder: JSONDecoder())
.receive(on: RunLoop.main)
.eraseToAnyPublisher()
}
func load() {
assert(Thread.isMainThread)
self.state = .loading(self.dataTask.sink(
receiveCompletion: { completion in
switch completion {
case .finished:
break
case let .failure(error):
self.state = .error(error)
}
},
receiveValue: { value in
self.state = .loaded
self.todos = value
}
))
}
func loadIfNeeded() {
assert(Thread.isMainThread)
guard case .ready = self.state else { return }
self.load()
}
}
struct TodosURLSessionExampleView: View {
@ObservedObject var model = TodosModel()
var body: some View {
List(model.todos) { todo in
Text(todo.title)
}
.overlay(StatusOverlay(model: model))
.onAppear { self.model.loadIfNeeded() }
}
}
struct StatusOverlay: View {
@ObservedObject var model: TodosModel
var body: some View {
switch model.state {
case .ready:
return AnyView(EmptyView())
case .loading:
return AnyView(ActivityIndicatorView(isAnimating: .constant(true), style: .large))
case .loaded:
return AnyView(EmptyView())
case let .error(error):
return AnyView(
VStack(spacing: 10) {
Text(error.localizedDescription)
.frame(maxWidth: 300)
Button("Retry") {
self.model.load()
}
}
.padding()
.background(Color.yellow)
)
}
}
}
struct TodosURLSessionExampleView_Previews: PreviewProvider {
static var previews: some View {
Group {
TodosURLSessionExampleView(model: TodosModel())
TodosURLSessionExampleView(model: self.exampleLoadedModel)
TodosURLSessionExampleView(model: self.exampleLoadingModel)
TodosURLSessionExampleView(model: self.exampleErrorModel)
}
}
static var exampleLoadedModel: TodosModel {
let todosModel = TodosModel()
todosModel.todos = [TypiTodo(id: 1, title: "Drink water"), TypiTodo(id: 2, title: "Enjoy the sun")]
todosModel.state = .loaded
return todosModel
}
static var exampleLoadingModel: TodosModel {
let todosModel = TodosModel()
todosModel.state = .loading(ExampleCancellable())
return todosModel
}
static var exampleErrorModel: TodosModel {
let todosModel = TodosModel()
todosModel.state = .error(ExampleError.exampleError)
return todosModel
}
enum ExampleError: Error {
case exampleError
}
struct ExampleCancellable: Cancellable {
func cancel() {}
}
}
关于ios - 使用 URLSession 为 SwiftUI View 加载 JSON 数据,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61855811/