Loading
BLOG 開発者ブログ

2020年2月5日

「SwiftUIの機能 @State, @ObservedObject, @EnvironmentObjectの違いとは」

SwiftUIにて登場する@State, @ObservedObject, @EnvironmentObjectの違いとは?
それぞれがどういったもので、どの場面で利用できるのかを解説します。

 

イントロ

こんにちは。モバイルソリューショングループのyamazaki.hです。
WWDC 2019で発表されたフレームワークSwiftUI。最近になってTutorialを実践しています。

SwiftUI Tutorialの実践についてはide.tさんの記事がありますので、よければこちらもどうぞ。
SwiftUIチュートリアルをやってみた!

今回の記事ではその中のアプリの状態管理の方法、
@State, @ObservedObject, @EnvironmentObjectといったProperty Wrappersがどういったもので、
どのように利用するのかを解説します。

 

対象読者

今回の記事の対象読者は以下を想定しています。

  • SwiftUI Tutorialを実践している方
  • SwiftUIを利用しているがProperty Wrappersの使い分けを再確認したい方

 

説明一覧

  1. @State
  2. @ObservedObject
  3. @EnvironmentObject

 

@State

SwiftUIでのViewはstructであるため保持するプロパティを変更することができません。
そこで、@Stateの出番です。
@Stateを付与したプロパティはメモリ管理がSwiftUIフレームワークに委譲され、変更が可能となります。
プロパティは値の変更が監視され、変更時に宣言されたViewのbodyが再描画されます。

ただし、そのプロパティへのアクセスは宣言されたView内でのみとなります。
また、値が変更されるようなアクセスでは’$’を付与する必要があります。

// HogeView.swift

import SwiftUI

struct HogeView: View {
    @State var showOnlyFavorite: Bool = false

    var body: some View {
        HStack {
            Toggle(isOn: $showOnlyFavorite) {
                Text("Show Favorite")
            }
            Text("State is \(String(showOnlyFavorite))")
        }
    }
}

struct HogeView_Previews: PreviewProvider {
    static var previews: some View {
        HogeView()
    }
}

ToggleがFALSEの状態。右にその値を表示しています。

ToggleをTRUEに。右の値が更新されました。

主に単一のView内での状態管理に利用できます。
例えば、リストのお気に入り表示の管理フラグなどが挙げられます。

 

@ObservedObject

これは@Stateとよく似たものになります。
@StateでViewの状態管理は可能ですが、複数の値を状態管理するとなるとプロパティの宣言が多くなり、管理が大変になります。
そこで、@ObservedObjectを利用すると、そういったプロパティをひとまとめにしたオブジェクトとして管理することができます。
付与するオブジェクトはObservableObjectプロトコルに準拠する必要があります。
また、監視するプロパティは@Publishedを付与します。

// ObservedFuga.swift

import SwiftUI

class ObservedFuga : ObservableObject {
    @Published var isDisp: Bool = false
    @Published var value = 0.0
}
// HogeView.swift

import SwiftUI

struct HogeView: View {
    @ObservedObject var object: ObservedFuga

    var body: some View {
        VStack {
            Toggle(isOn: self.$object.isDisp) {
                Text("Show Favorite")
            }
            Slider(value: self.$object.value, in: 0...100)
            if (self.object.isDisp) {
                Text("Value is \(self.object.value)")
            }

        }
    }
}

struct HogeView_Previews: PreviewProvider {
    static var previews: some View {
        HogeView(object: ObservedFuga())
    }
}

ToggleがFALSEの状態。

ToggleがTRUEの状態、Sliderの値が表示されました。

ToggleがTRUEの状態、Sliderを動かすと下の値が更新されます。

こちらも@Stateと同様単一のView内での状態管理に利用します。
クラスとして定義するため、複数のViewで使い回すことができます。
それゆえ、ViewModelとしての使い方が挙げられます。
注意点としてViewの生成時に引数として渡す必要があります。

 

@EnvironmentObject

こちらは先ほどの@Stateや@ObservedObjectとは少し異なります。
というのも、@EnvironmentObjectを付与したプロパティは複数のViewで共通のインスタンスを参照します。
つまり、アプリ全体で共通のプロパティとなります。

// ObservedFuga.swift

import SwiftUI

class ObservedFuga : ObservableObject {
    @Published var isDisp: Bool = false
    @Published var value = 0.0
}
// HogeView.swift

import SwiftUI

struct HogeView: View {
    @EnvironmentObject var object: ObservedFuga

    var body: some View {
        VStack {
            Toggle(isOn: self.$object.isDisp) {
                Text("Show Favorite")
            }
            Slider(value: self.$object.value, in: 0...100)
        }
    }
}

struct HogeView_Previews: PreviewProvider {
    static var previews: some View {
        HogeView()
            .environmentObject(ObservedFuga())
    }
}
// FugaView.swift

import SwiftUI

struct FugaView: View {
    @EnvironmentObject var environment: ObservedFuga

    var body: some View {
        Text("value is \(self.environment.value)")
    }
}

struct FugaView_Previews: PreviewProvider {
    static var previews: some View {
        FugaView()
            .environmentObject(ObservedFuga())
    }
}
// MainView.swift

import SwiftUI

struct MainView: View {
    var body: some View {
        VStack {
            HogeView()
            FugaView()
        }
        .environmentObject(ObservedFuga()) // この行でEnvironmentObjectを設定している
    }
}

struct MainView_Previews: PreviewProvider {
    static var previews: some View {
        MainView()
    }
}

HogeViewとFugaViewが表示されています。

HogeViewでSliderの値を変更することで、FugaViewが表示している値が更新されます。

上記のコードにあるように、単純に宣言するだけでは利用することができません。
アプリ全体で利用するため、Delegateで設定する必要があります。

// SceneDelegate.swift

// some codes...

  func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
    // Create the SwiftUI view that provides the window contents.
    let contentView = MainView()
        .environmentObject(ObservedFuga()) // この行で設定している

    // Use a UIHostingController as window root view controller.
    if let windowScene = scene as? UIWindowScene {
        let window = UIWindow(windowScene: windowScene)
        window.rootViewController = UIHostingController(rootView: contentView)
        self.window = window
        window.makeKeyAndVisible()
    }
  }

// some codes...

 

まとめ

それぞれの違いを表にまとめました。

 

あとがき

@Stateや@ObservedObject, @EnvironmentObjectを利用することで、Viewの状態管理と処理との分離ができViewの実装が簡単になりました。
しかし初めてみたときにはこれらの違いがなかなか理解できず、Tutorialを進める手が止まってしまいました。
私と同じ、そういった方にとってこの記事がSwiftUIの学習を進める助けになれば幸いです。

 

yamazakihのブログ