LoginSignup
8

More than 3 years have passed since last update.

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

Posted at

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で隠しても良いと思います。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8