Listに表示した情報をリロードする

Swift
Listに表示した情報をリロードする

はじめに

UIListViewControllerを引っ張ってリロードする仕組みをSwiftUIでやってみました。
リロード処理はボタンのタップで走らせるのが楽ちんです。しかしながら、UI的には引っ張ってリロードのほうが良さげ(個人的主観)なので、試してみたいと思います。

UIScrollViewのビューをラップする

makeUIViewメソッドを作る

非同期通信中にActivityIndicatorを表示させてみる時と同様に、UIViewRepresentableプロトコルに準拠したSwiftUIのビューを作ります。ラップするのはUIScrollViewです。

func makeUIView(context: Context) -> UIScrollView {
  let control = UIScrollView()
  return control
}

ただこれでは引っ張ってリロードができないので、UIRefreshControlrefreshControlプロパティに代入しておきます。また、valueChangedイベントが起こったときにリロード処理が実行されるようaddTargetしておきます。

func makeUIView(context: Context) -> UIScrollView {
    let control = UIScrollView()

  control.refreshControl = UIRefreshControl()
  control.refreshControl?.addTarget(context.coordinator, action:nil, for: .valueChanged)

    return control
}

context.coordinatorという見慣れないものがありますが、後ほど。

Coordinatorクラス登場!

UIRefreshControlのイベントをSwiftUIへ伝えるためにCoordinatorクラスを実装します。この点が非同期通信中にActivityIndicatorを表示させてみる時と違うところです。

このCoordinatorクラスはmakeUIViewメソッド実行時に、makeCoordinatorメソッドにて1度だけ生成されます。

Coordinatorクラスの実装

Coordinatorクラスのスッピンバージョンがこちらです。

class Coordinator: NSObject {
    var view: [ラップされたビュー]

    init(_ view: [ラップされたビュー]) {
        self.view = view
    }
}

コーディネート対象のビューはRefreshViewです。そして、UIRefreshControlhandleRefreshControlメソッドを実行したいので、このクラス内に実装します。

class Coordinator: NSObject {
    var view: RefreshView

    init(_ view: RefreshView) {
        self.view = view
    }

    @objc func handleRefreshControl(sender: UIRefreshControl) {
        sender.endRefreshing()
    }
}

CoordinatorクラスをmakeCoordinatorメソッドでRefreshViewに保持させる

保持させるという表現が合っているのかどうかはちょっと不安。ここはあえて「保持する」という表現で行きます。

CoordinatorクラスをRefreshViewで保持するためにmakeCoordinatorメソッドを実装します。

func makeCoordinator() -> Coordinator {
  Coordinator(self)
}

非常にシンプル。UIViewRepresentableプロトコルに準拠させておけば、makeCoordinatorメソッドはmakeUIViewメソッド実行時に自動で1回だけ呼ばれます。やるべきことはこれだけです。

ラップしたビューでイベントが実行されるようにする

仕上げです。Coordinatorクラスで実装したメソッドをラップしたビューで実行されるようにします。
makeUIViewメソッドにて記述したaddTargetに呼び出すメソッドを記述します。

control.refreshControl?.addTarget(context.coordinator, action:#selector(Coordinator.handleRefreshControl), for: .valueChanged)

ここでcontext.coordinatorですよ!いつものcontextは文脈的なあれです。文脈的な流れ上、contextcoordinatorプロパティにはmakeCoordinatorメソッドで生成したCoordinatorクラスが入っています(入っているはずです)。

まとめ

思ったよりもあっさりできました。引っ張ったら一瞬インジケータ(くるくる)が表示されます。

ラップしたビューはUIScrollViewなので、ここまでで用意したビューに別のビューを載せて、handleRefreshControlメソッドにリロード時の処理を追加すればOKです。

例えば、Listビューを載せて、handleRefreshControlメソッドにDBからのデータ取得処理を実行するなどです。ちなみに、Listビューでデータ取得処理を監視しておいて、データが流れてきたらリストに表示するのは非同期通信中にActivityIndicatorを表示させてみる時の応用でできそうです。

SwiftUI側とラップされるUIKit側双方向のイベントを処理できるようになりました。活用の幅が広がりそうですね。