はじめに
UIKitで提供されているUIのUIActivityIndicatorView
をSwiftUIで使えるようにラップしました。せっかく作ったので、使ってみましょう。
目標
これまで散々弄り倒してきたMongoDBからドキュメントを操作するアプリを使います。
現状は、リクエストを出して結果が返ってくるまでの間、非同期通信を行っていますが、バックグラウンド処理なので見かけ上何も起こっていないように見えます。
ドキュメントの量や通信環境が良好ならば、結果が返ってくるまで時間がかからないと思います。しかしながら、状況によっては結果が返ってくるまで時間がかかるかもしれません。そのときにはちゃんと作業中であることの意思表示は必要です。返ってきた情報を使って後続の処理が行われるときは、返ってくるまで待たなくては行けないですし。
ですので、せめて作業中であることの意思表示として、UIActivityIndicatorView
をラップしたActivityIndicator
を表示させます。代表でGETリクエストを出してドキュメントを全件取得するメソッドで示します。
元の状態
通信処理側
class Access2Mongo:NSObject {
func get() {
let url: URL = URL(string: [接続先アドレス] + "/api/books/")!
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
let task:URLSessionTask = URLSession.shared.dataTask(with: request) { (data, response, error) in
if let dataString = data {
print("data:" + (String(data: dataString, encoding: .utf8) ?? "nil"))
}
print("response: \(String(describing: response))")
print("error: \(String(describing: error))")
}
task.resume()
}
}
ネーミングセンスは相変わらずです。
UI側
struct ContentView: View {
var access2Mongo:Access2Mongo = Access2Mongo()
var body: some View {
VStack {
Button(action: {
self.access2Mongo.get()
}) {
Text("Get Document")
}
}
}
}
方針
UI側では状態を保持せず、通信処理側から通知を出すことでUIの制御をしていく方針で進めます。
通信処理側
ObservableObjectプロトコルに準拠させる
データを保持する側のクラスをObservableObjectプロトコルに準拠させます。準拠させると言っても、ここでは保持しているデータが変わる都度自動的に通知するだけなので、特にメソッドを実装したりする必要はありません。
通知対象のプロパティに@Publised属性をつける
通知したいデータを保持しているプロパティには@Published
属性を付けておきます。これを付けておくことで、プロパティの値が変わる都度、SwiftUIに対して自動的に通知を出してくれます。
今回は、作業中であるかどうかを示すものと、リクエストを送って得られた結果を保持するものの2つを用意したいと思います。
プロパティに値を代入する
用意したプロパティに値を入れていきます。値を入れないと、せっかく通知対象にしたのに何も起こらずしょんぼりします。
処理開始時点と終了時点、返ってきた結果を代入します。
なお、終了時点のプロパティ設定時は、メインスレッドで行ってください。
最終的なコード
class Access2Mongo:NSObject {
@Published var data:Data = Data()
@Published var isProgress:Bool = false
func get() {
self.isProgress = true
let url: URL = URL(string: [接続先アドレス] + "/api/books/")!
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
let task:URLSessionTask = URLSession.shared.dataTask(with: request) { (data, response, error) in
if let dataString = data {
self.data = dataString
print("data:" + (String(data: dataString, encoding: .utf8) ?? "nil"))
}
print("response: \(String(describing: response))")
print("error: \(String(describing: error))")
DispatchQueue.main.async {
self.isProgress = false
}
}
task.resume()
}
}
UI側
通知が送られてくるクラスに@ObservedObject属性を付ける
さてお次は通知を受け取る側です。通知を送ってくるクラスのインスタンスに@ObservedObject
属性を付けて、プロパティの変更を受け取ることができるようにします。
プロパティの値を利用する
普通にプロパティの値を参照しておくだけです。これだけでプロパティの値が変わると自動的にUIが更新されます。
ActivityIndicator
のisAnimating
に非同期通信の開始と終了を通知し、リクエストの結果を受け取ったら表示するようにしておきます。
最終的なコード
var body: some View {
@ObservedObject var access2Mongo:Access2Mongo = Access2Mongo()
VStack {
ActivityIndicator(isAnimating: $access2Mongo.isProgress, hidesWhenStopped: true, style: .large, color: .purple)
Button(action: {
self.access2Mongo.get()
}) {
Text("Get Document")
Text("\(access2Mongo.data)")
}
}
動かしてみる
肝心のぐるぐるは、はぐれメタル級の素早さで消えたため、撮れませんでした。
リクエスト結果をそのままData
そのままを持ってきたためただのダンプになっていますが、ご了承ください。
まとめ
無事非同期通信にぐるぐる(ActivityIndicator
)を表示させることができました。
状態保持とデータのフローはちょっとややこしかったです。Combine
フレームワークはきっちりと押さえておく必要がありそうです。
余談
はぐれメタルのようなぐるぐるを捉えんがため、sleep
メソッドで少し余裕をもたせることにしてみました。結果としてぐるぐるを捉えることができたのですが、引数がミリ秒だと思い込んでいて2000とか入れてしまい、消えないぐるぐるをしばらく眺めていたのは内緒です。引数がミリ秒なのはusleep
メソッドでしたね。