👀

【SwiftUI】@ObservedObjectを使うと再描画で画像がチカチカする件

2022/09/28に公開

画像がチカチカってどういう状況?

Firebaseから取ってきた画像を、 ViewModelを通して、@ObservedObjectを使って、Viewとデータバインディングして表示しようとすると、再描画の際に画像の表示が少し遅れて、画像がチカチカするような現象が起きて困っていました。

こんな感じです。(gifなのでまだマシに見えますが、実際はもっとガックガックしてます。)
変更前

このときのViewのソースコードはこんな感じです。

View.swift
import SwiftUI

struct SelectReferenceView: View {
    @EnvironmentObject var registerVM: RegisterViewModel
    @ObservedObject var referenceVM = ReferenceViewModel()
    
    var body: some View{
        VStack {
            // 「参照元を選択」
            Text("参照元を選択")
                .modifier(SectionTitle())
                .padding(.vertical, 15)
            
            // カバー画像 表示部
            HStack() {
                ForEach(referenceVM.references) { reference in
                    VStack(spacing: 5) {
                        // 参照元のカバー画像
                        Image(uiImage: reference.image)
                            .coverImageExtension()
                            
                        // 参照元のタイトル
                        Text(reference.title)
                            .modifier(ReferenceTitle())
                        
                        Spacer()
                    }
                    .padding(.horizontal, 10)
                }
            }
            .padding(.top, 15)
        }
        
        Form {
            HStack {
                Text("メモ1")
                    .frame(width: FrameSize().width * 0.2, alignment: .leading)
                TextField(text: $registerVM.memo1, prompt: Text("任意(例:ページ数)")) {}
                
            }
            HStack{
                Text("メモ2")
                    .frame(width: FrameSize().width * 0.2, alignment: .leading)
                TextField(text: $registerVM.memo2, prompt: Text("任意(例:品詞)")) {}
            }
        }
    }
}

原因

データバインディングの種類と違いを理解せずに、盲目的に@ObservedObjectを使用していたことが問題でした。

SwiftUIにおけるデータバインディングには以下の4つの種類と特徴があります。

  • @State: プロパティとViewを紐付ける
  • @StateObject: データクラスとViewを紐付ける(Viewの再構築でもインスタンスが破棄されない)
  • @EnvironmentObject: データクラスとViewを紐付ける(複数Viewやアプリケーション全体に渡ってデータを共有できる)
  • @ObservableObject: データクラスとViewを紐付ける(Viewの再構築でインスタンスが破棄される)

上記の通り、@ObservalObjectを使用すると、FormのTextfieldを選択したり、値を入力したりする度に、Viewが再構築されインスタンスが破壊&生成されるために、画像が何度も再描画されチカチカするという現状が起こってしまっていたようです。

解決策

Viewの再構築の際にインスタンスが破壊されることのない、@StateObjectを使用するようにしました。

変更後

View.swift
import SwiftUI

struct SelectReferenceView: View {
    
    @EnvironmentObject var registerVM: RegisterViewModel
    // 変更前: @ObservedObject var referenceVM = ReferenceViewModel()
    @StateObject var referenceVM = ReferenceViewModel()

// 以下のソースコードは冒頭のものと同じ...

}

参考文献

Discussion