SwiftUIでWebAPI等を呼んでViewを更新するやり方です。
単に更新するだけではなく、エラーだったらAlertを表示したり、読み込み中はインジゲータを表示するようにしたいと思います。
何か非同期処理をする ObservableObject
を作ります。
class APILoader<T: Decodable>: ObservableObject { let api: API @Published var result: Result<T, Error>? = nil init(api: API) { self.api = api load() } func load() { result = nil api.get { (result: Result<T, Error>) in self.result = result } } }
非同期処理が完了すると result
が更新されるようにしておいて、それを @Published
にしておきます。
View は↓のようなかんじになります。
struct LoadableView<T: Decodable, V: View>: View { @State var showingAlert = true @ObservedObject var loader: APILoader<T> let content: (T) -> V var body: some View { guard let result = loader.result else { return AnyView(IndicatorView()) } switch result { case .success(let result): return AnyView(content(result)) case .failure(let error): return AnyView( Text("再読込") .alert(isPresented: $showingAlert) { Alert(title: Text(""), message: Text(error.localizedDescription)) } .padding(20) .onTapGesture(count: 1) { self.loader.load() } ) } } }
loader.result
が nil- ロード中:
IndicatorView
を表示
- ロード中:
loader.result
が success- 成功:
content()
に結果を渡して表示
- 成功:
loader.result
が failure- 失敗: "再読込"を表示してエラー内容をアラートで表示
というような挙動です。
使う時は
LoadableView(loader: APILoader(api: .home)) { HomeView(home: $0) })
のような形で、APILoader と View を渡すことで汎用的に使うことができます。
content
にクロージャを渡して拡張していくのが SwiftUI ぽいのかなと思っています。