Help us understand the problem. What is going on with this article?

[SwiftUI]UIViewRepresentableにおける差分更新を最適化する

More than 1 year has passed since last update.

SwiftUIでは、UIViewをSwiftUIの世界に持ち込む際にUIViewRepresentableを利用します。
UIViewRepresentableはラップするUIViewのassociatedtypeと、UIViewの生成と更新を行う関数を保証するprotocolです。

public protocol UIViewRepresentable : View where Self.Body == Never {
    associatedtype UIViewType : UIView
    func makeUIView(context: Self.Context) -> Self.UIViewType
    func updateUIView(_ uiView: Self.UIViewType, context: Self.Context)
}

UIKitを使っていると、このUIViewRepresentableこそがビューの実体であるように思えますがUIViewRepresentableはあくまでもUIViewの生成と更新を行うだけでありUIViewのインスタンスはSwiftUIが独自に管理することになります。

さて、SwiftUIはビュー構造の中で変数が更新されるとbodyを再評価します。
通常SwiftUIのコンポーネントは内部的に差分の確認を行いますが、UIViewRepresentableの場合は無条件に更新関数がコールされます。

つまり更新関数の中で負荷の高い処理が行われている場合、その処理の結果が等価である場合も処理が通過してしまうということです。
これを防ぐ方法として、最終的にUIViewに渡す変数を全て保持しておけば良いですが実際はUIViewRepresentable側に隠しておきたいこともあると思います。

私の場合は、hashValueを元に前回の更新内容を比較して同じであれば処理をしないような以下のprotocolを定義しました。

protocol UIViewOptimizedRepresentable: UIViewRepresentable {
    associatedtype Value: Hashable
    var value: Value { get }
    init(_ value: Value)
    func shouldUpdateUIView(_ uiView: Self.UIViewType, context: Self.Context)
}

extension UIViewOptimizedRepresentable {
    func updateUIView(_ uiView: Self.UIViewType, context: Self.Context) {
        guard uiView.tag != self.value.hashValue else { return }
        shouldUpdateUIView(uiView, context: context)
        uiView.tag = self.value.hashValue
    }
}

hashValueはtagに隠していますが、気に入らない方はObjCRuntimeで隠しても良いと思います。

noppefoxwolf
きつねすき、にんげんこわい
https://noppelab.com
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away