【SwiftUI】@ObservedObjectと@EnvironmentObjectの違い

こんにちはコーヤです。

このページでは、SwiftUIの@ObservedObjectと@EnvironmentObjectの違いをご紹介します。@ObservedObjectは別インスタンスで@EnvironmentObjectは同インスタンスのイメージです。

以下のバージョンで動作確認しています。

  • Xcode 14.2
  • Swift 5.7.2

実現したいこと

“Button1″と”Button2″を押した回数をカウントします。

それぞれのボタンを押した回数を

  • 別々に集計するパターン(@ObservedObject)
  • 一緒に集計するパターン(@EnvironmentObject)

の2パターンで実装します。

は一緒に集計するパターンで使用します。

別々に集計するパターン(@ObservedObject)

swiftファイル以下の4つでアプリを構成します。

  • ContentView.swift
  • View1.swift
  • View2.swift
  • CountClass.swift

それぞれコードは以下のとおりです。

import SwiftUI

struct ContentView: View {
    
    var body: some View {
        VStack {
            View1()
            Divider()
            View2()
        }
        .font(.largeTitle)
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
import SwiftUI

struct View1: View {
    
    @ObservedObject var countClass: CountClass = CountClass()
    
    var body: some View {
        HStack(spacing: 50) {
            Button("Button1") {
                countClass.countUp()
            }
            Text("Count = \(countClass.count)")
        }
    }
}

struct View1_Previews: PreviewProvider {
    static var previews: some View {
        View1()
    }
}
import SwiftUI

struct View2: View {
    
    @ObservedObject var countClass: CountClass = CountClass()
    
    var body: some View {
        HStack(spacing: 50) {
            Button("Button2") {
                countClass.countUp()
            }
            Text("Count = \(countClass.count)")
        }
    }
}

struct View2_Previews: PreviewProvider {
    static var previews: some View {
        View2()
    }
}
import Foundation

class CountClass: ObservableObject {
    
    @Published var count: Int = 0
    
    func countUp() {
        self.count += 1
    }
}

View1とView2はどちらもCountClassを@ObservedObjectとして宣言しているため、View1とView2のcountClassは別のインスタンスになります。

したがって、ボタンを押した回数も別々に集計されます。

一緒に集計するパターン(@EnvironmentObject)

同じようにswiftファイル以下の4つでアプリを構成します。

  • ContentView.swift
  • View1.swift
  • View2.swift
  • CountClass.swift

それぞれコードは以下のとおりです。

import SwiftUI

struct ContentView: View {
    
    var body: some View {
        VStack {
            View1()
            Divider()
            View2()
        }
        .font(.largeTitle)
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
import SwiftUI

struct View1: View {

    @EnvironmentObject var countClass: CountClass
    
    var body: some View {
        HStack(spacing: 50) {
            Button("Button1") {
                countClass.countUp()
            }
            Text("Count = \(countClass.count)")
        }
    }
}

struct View1_Previews: PreviewProvider {
    static var previews: some View {
        View1()
    }
}
import SwiftUI

struct View2: View {

    @EnvironmentObject var countClass: CountClass
    
    var body: some View {
        HStack(spacing: 50) {
            Button("Button2") {
                countClass.countUp()
            }
            Text("Count = \(countClass.count)")
        }
    }
}

struct View2_Previews: PreviewProvider {
    static var previews: some View {
        View2()
    }
}
import Foundation

class CountClass: ObservableObject {
    
    @Published var count: Int = 0
    
    func countUp() {
        self.count += 1
    }
}

View1とView2はどちらもCountClassを@EnvironmentObjectとして宣言しているため、View1とView2のcountClassは同じインスタンスになります。

したがって、ボタンを押した回数は一緒に集計されます。

@EnvironmentObjectを使用したときの注意点として、アプリをビルドする前に@mainの部分に.environmentObject()のモディファイアを加える必要があります。

下記のコードの8行目の部分を加えます。

import SwiftUI

@main
struct SampleAppApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(CountClass())
        }
    }
}

また、@EnvironmentObjectを使用するとXcodeのキャンパス(画面右側のプレビューするエリア)がエラーになります。

この場合はContentView_Previewsにも.environmentObject()のモディファイアを加えるとキャンパスのエラーがなくなります。

以上です。ご参考になれば幸いです。

コメント欄