はじめに
WWDC始まってますね!毎年の楽しみです。睡眠不足が身体に堪えるので、徹夜は控えています。なので、一足遅れて情報が入ってくる状態です。早くキャッチアップして遅れを取り戻していきたいですね。
さて、もしかしたらすでにSwftUIに用意されているかもしれないですが、UIActivityIndicatorView
をSwiftUIでも使えるようにしてみたいと思います。
「小並感」に比べて、より一層実用的です。
UIActivityIndicatorViewをSwiftUI用にラップする
UIViewRepresentableに準拠した構造体を作る
struct ActivityIndicator:UIViewRepresentable {
}
できました。エラーが出ると思いますが安心してください。これから作っていきます。
ビューで保持する必要があるプロパティを用意する
さっき作った構造体にビューで保持する必要があるプロパティを用意していきます。UIActivityIndicatorView
が持つプロパティは以下の通り。
- isAnimating(ぐるぐるしているかどうかを表す)
- hidesWhenStopped(ぐるぐるが止まったときにインジケータを隠すかどうかを指定する)
- style(インジケータの大きさを指定する)
- color(インジケータの色を指定する)
このうち、isAnimating
はSwiftUIとUIKitの間で値を共有するために使おうと思いますので、@Binding
属性を付けておきます。これは、本家UIKitでの使い方とちょっと違います。UIActivityIndicatorView
を動かすか止めるかは、startAnimating()
メソッドとstopAnimating()
メソッドで行いますが、SwiftUIではビュー間でそれらのメソッドを呼び出せません。なので、プロパティを介して動かすか止めるかを制御することにしました。
これを踏まえて、構造体に追記していきます。
struct ActivityIndicator:UIViewRepresentable {
@Binding var isAnimating: Bool
public var hidesWhenStopped = false
public var style = UIActivityIndicatorView.Style.medium
public var color = UIColor.gray
}
名前をUIKit側と合わせておきました。また、適宜初期値を与えておきました。
まだXCode上ではエラーが出ていて落ち着かないですが、もう少し我慢してください。
UIKitのビューをSwiftUI向けに提供するmakeUIViewを作る
UIKitのビューをSwiftUI向けに生成するメソッドを作ってみます。これは構造体をUIViewRepresentable
に準拠させるために必要なものです。
このメソッドは直接呼ぶのではなく、イニシャライザを呼び出したときに裏で勝手に呼ばれます。
struct ActivityIndicator:UIViewRepresentable {
@Binding var isAnimating: Bool
public var hidesWhenStopped = false
public var style = UIActivityIndicatorView.Style.medium
public var color = UIColor.gray
func makeUIView(context: Context) -> UIActivityIndicatorView {
let indicator = UIActivityIndicatorView(style: self.style)
indicator.color = self.color
indicator.hidesWhenStopped = self.hidesWhenStopped
return indicator
}
}
各プロパティを与えて初期化しています。makeUIView
メソッドにより、内々でUIKitのビューのインスタンスを生成しています。
SwiftUIでの変化をUIKitのビューに伝えるupdateUIViewを作る
仕上げです。@Binding
属性が付けられたプロパティisAnimating
の値が変化するたびに呼び出されるupdateUIView
メソッドを作っていきます。
struct ActivityIndicator:UIViewRepresentable {
@Binding var isAnimating: Bool
public var hidesWhenStopped = false
public var style = UIActivityIndicatorView.Style.medium
public var color = UIColor.gray
func makeUIView(context: Context) -> UIActivityIndicatorView {
let indicator = UIActivityIndicatorView(style: self.style)
indicator.color = self.color
indicator.hidesWhenStopped = self.hidesWhenStopped
return indicator
}
func updateUIView(_ uiView: UIActivityIndicatorView, context: Context) {
if isAnimating {
uiView.startAnimating()
}
else {
uiView.stopAnimating()
}
}
}
使ってみる
うまく動くか試してみます。適当にドドドッと。
VStack {
Text("Hello, World!")
Toggle(isOn: $isAnimating) {
Text("isAnimating")
}
ActivityIndicator(isAnimating: .constant(false))
ActivityIndicator(isAnimating: .constant(true))
ActivityIndicator(isAnimating: $isAnimating, hidesWhenStopped: true, style: .medium, color: .orange)
ActivityIndicator(isAnimating: $isAnimating, hidesWhenStopped: true, style: .large, color: .purple)
ActivityIndicator(isAnimating: $isAnimating, hidesWhenStopped: false, style: .medium, color: .red)
ActivityIndicator(isAnimating: $isAnimating, hidesWhenStopped: false, style: .large, color: .blue)
}
ちゃんとインジケータのプロパティも効いています。画像は止まっていますが、ちゃんと回っていました。
まとめ
これでSwiftUIでもカラフルなインジケータを表示させることができるようになりました。これを使えば重たい処理の間にぐるぐる三昧!同様の手順で、他のUIKitもラップしてSwiftUIで使えるようになると思います。SwiftUIを使う前から作りためていたものをどんどんラップしてSwiftUIでも使ってみましょう。
ただし、ここで示した手順はUIKit側からSwiftUI側へイベントなどを伝える必要がないパターンなのでご注意を!