UIViewRepresentableにおける差分検出をどこで行うか

2024/04/26に公開

UIViewRepresentableを使うことで、SwiftUIの世界にUIKitのビューを持ち込むことができます。
UIViewRepresentableは以下のメソッドを要求するprotocolです。

func makeUIView(context: Self.Context) -> Self.UIViewType
func updateUIView(_ uiView: Self.UIViewType, context: Self.Context)

さて、ここで次のようなSwiftUIのbodyがあったとしましょう。

var body: some View {
  UIKitView()
    .environment(\.color, .red)
    .environment(\.font, .bold)
}

すると、UIKitViewfunc updateUIView(_ uiView: Self.UIViewType, context: Self.Context)の中でenvironmentの値をUIKitのビューに反映させることになります。

uiView.color = context.environment.color
uiView.font = context.environment.font

ここで問題になるのは、updateUIViewがどの値の変更によって呼ばれたのか分からないという点です。
値をセットすることが高コストなAPIの場合、無関係の値の更新によって毎回高コストなAPIが呼ばれることになります。

これを回避するには、updateUIViewの中で前回の値と同じであれば高コストの処理をスキップする必要があります。
この処理は、本来はUIKitのビュー側で行うべき処理なので、updateUIViewに直に書かず、以下のようにoldValueを参照して後続の処理を実行します。
副次的にUIKitで直接利用する場合でもパフォーマンスが最適化されます。

var color: ColorType = .default {
  didSet {
    if color != oldValue {
      updateProcess()
    }
  }
}

ピュアなSwiftUIの場合、bodyの中での差分最適化に注目しますが、UIViewRepresentableの場合はUIKitに頭を切り替える必要があります。

Discussion