今回は
環境は,
- macOS Catalina 10.15.7
- Xcode 12.0.1
- Swift 5.3
- iOS 14.0
です.
EnvironmentObjectとは
State
のところでも話したようにViewは構造体のため, 基本的に値の変更や更新ができません. そこでView内のプロパティの変更を可能にするのが@State
というプロパティラッパーでした.
ただState
はView内でしか使うことができませんので, 自分で作成したクラスの値を利用したい場合は用いることができません.
そこで登場したのがObservedObject
でした.
observedobjectのブログ
ただ, ObservedObject
の場合, 最下位層で値を参照にしたい場合, その引継ぎが大変になります.
例えば, 以下のコードはView→SubView1→SubView2→SubView3という層になっていて,
- Viewで定義したcountをSubView1に引き渡す
- SubView1で受け取ったcountをSubView2に引き渡す
- SubView2で受け取ったcountをtextで表示する
といった形になっています.
import SwiftUI
// Numberクラスの定義
class Number: ObservableObject {
//回数をカウントするプロパティの設定
@Published var num = 0
}
struct ContentView: View {
//インスタンスを生成
@ObservedObject var number = Number()
var body: some View {
VStack(spacing: 20.0) {
//ボタンの作成
Button(action: {
//ボタンが押されるたびに+1していく
number.num += 1
}) {
Text("ボタンを押してください")
.fontWeight(.bold)
.foregroundColor(Color.blue)
.padding(10)
}
.background(Color.pink)
.border(Color.blue)
.padding(.bottom, 30)
//SubView1の作成
SubView1(n1: number)
}
.font(.title2)
}
}
//SubView1の定義
struct SubView1:View {
@ObservedObject var n1 : Number
var body: some View {
VStack {
SubView2(n2: n1)
}
.padding(15)
.background(Color.blue)
}
}
//SubView2の定義
struct SubView2:View {
@ObservedObject var n2 : Number
var body: some View {
VStack {
SubView3(n3: n2)
}
.padding(15)
.background(Color.green)
}
}
//SubView3の定義
struct SubView3:View {
@ObservedObject var n3 : Number
var body: some View {
VStack {
Text("カウント数:\(n3.num) ")
}
.padding(10)
.background(Color.yellow)
}
}

コードの装飾部分(モディファイア)を取り除き, コードの仕組みを見ると値の受け渡しが確認しやすいかと思います.

このように, Viewの中のSubViewで値を参照するのはとても大変です.
それを解決する1つの方法が今回紹介するEnvironmentObject
の利用です. EnvironmentObject
はView内で定義するState
やClass内で定義するObservedObject
とは違い,
- Life CycleがSwiftUI Appの場合→ProjectName.swift
- Life CycleがUIKit App Delegate Appの場合→SceneDelegate.swift
で定義します.

簡単な例(SwiftUI Appの場合)

SwiftUI AppはWWDC2020で発表されたSwiftUIの新しいLife Cycleです. UIKitのAppDelegateとSceneDelegateは使われなくなるんでしょうか…
作成するものは先ほどと同じカウントアプリです. (勝手に命名.)
EnvironmentObject
を使うため, WindowGroupのファイルのContentView()部分を書き換えます.
import SwiftUI
@main
struct SimpleAppApp: App {
var body: some Scene {
WindowGroup {
ContentView().environmentObject(Number())
}
}
}
以下でNumberクラスの登録を行います.
ContentView().environmentObject(Number())
import SwiftUI
// Numberクラスの定義
class Number: ObservableObject {
//回数をカウントするプロパティの設定
@Published var num = 0
}
struct ContentView: View {
@EnvironmentObject var number: Number
var body: some View {
VStack(spacing: 20.0) {
//ボタンの作成
Button(action: {
//ボタンが押されるたびに+1していく
number.num += 1
}) {
Text("ボタンを押してください")
.fontWeight(.bold)
.foregroundColor(Color.white)
.padding(10)
}
.background(Color.pink)
.border(Color.blue)
.padding(.bottom, 30)
//SubView1の作成
SubView1()
}
.font(.title2)
}
}
//SubView1の定義
struct SubView1:View {
var body: some View {
VStack {
SubView2()
}
.padding(15)
.background(Color.blue)
}
}
//SubView2の定義
struct SubView2:View {
var body: some View {
VStack {
SubView3()
}
.padding(15)
.background(Color.green)
}
}
//SubView3の定義
struct SubView3:View {
@EnvironmentObject var number: Number
var body: some View {
VStack {
Text("カウント数:\(number.num) ")
}
.padding(10)
.background(Color.yellow)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
今回もコードの装飾部分を取り除き, その仕組みを確認します.

簡単な例(UIkit App Delegateの場合)
SceneDelegate.swiftのscene関数内に以下のコードを追加します.
let number = Number()
この記述でNumberクラスのインスタンスを生成しています. また,
window.rootViewController = UIHostingController(rootView: contentView)
を
window.rootViewController = UIHostingController(rootView: contentView.environmentObject(number))
と書き換えます. この記述で環境変数空間に生成したインスタンスnumberを登録します.

ContentView.swiftのほうはSwiftUI Appのコードと同じです.
これでUIKit Delegateのほうも動かすことができます.
ライブプレビューが機能しない?
SwiftUI AppでもUIKit App DelegateでもLive Previewは起動しませんでした.
Cannot preview in this file – Failed update preview.
とエラーが出ます.


シュミレータのほうは起動します.
うーーーーん. もしやこれは重大なバグではないのか…
違いました. 少し調べたらSwiftUI APPでは単純なミスとわかりました.
ContentView Previewsのほうに環境変数空間にインスタンスを登録をしていませんでした.
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView().environmentObject(Number())
}
}

で解決しましたが,
UIkit App Delegateのほうではうまくできませんでした
まだまだ勉強不足でこれは解決にいたりませんでした.
コメント