SwiftUI入門-勉強メモ009-【プロパティ共有その3 EnvironmentObject】

今回は

環境は,

  • 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という層になっていて,

  1. Viewで定義したcountをSubView1に引き渡す
  2. SubView1で受け取ったcountをSubView2に引き渡す
  3. 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)
    }
}
SwiftUI

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

SwiftUI

このように, Viewの中のSubViewで値を参照するのはとても大変です.

それを解決する1つの方法が今回紹介するEnvironmentObjectの利用です. EnvironmentObjectはView内で定義するStateやClass内で定義するObservedObjectとは違い,

  • Life CycleがSwiftUI Appの場合→ProjectName.swift
  • Life CycleがUIKit App Delegate Appの場合→SceneDelegate.swift

で定義します.

SwiftUI

簡単な例(SwiftUI Appの場合)

Pierrotのアイコン画像Pierrot

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()
    }
}

今回もコードの装飾部分を取り除き, その仕組みを確認します.

SwiftUI

簡単な例(UIkit App Delegateの場合)

SceneDelegate.swiftのscene関数内に以下のコードを追加します.

let number = Number()

この記述でNumberクラスのインスタンスを生成しています. また,

window.rootViewController = UIHostingController(rootView: contentView)

window.rootViewController = UIHostingController(rootView: contentView.environmentObject(number))

と書き換えます. この記述で環境変数空間に生成したインスタンスnumberを登録します.

SwiftUI
SceneDelegate.swift

ContentView.swiftのほうはSwiftUI Appのコードと同じです.

これでUIKit Delegateのほうも動かすことができます.

ライブプレビューが機能しない?

SwiftUI AppでもUIKit App DelegateでもLive Previewは起動しませんでした.

Cannot preview in this file – Failed update preview.

とエラーが出ます.

SwiftUI
SwiftUI Appのほうでのエラー画面
SwiftUI

シュミレータのほうは起動します.

うーーーーん. もしやこれは重大なバグではないのか…

違いました. 少し調べたらSwiftUI APPでは単純なミスとわかりました.

ContentView Previewsのほうに環境変数空間にインスタンスを登録をしていませんでした.

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView().environmentObject(Number())
    }
}
SwiftUI

で解決しましたが,

UIkit App Delegateのほうではうまくできませんでした

まだまだ勉強不足でこれは解決にいたりませんでした.

よかったらシェアしてね!

コメント

コメントする

目次
閉じる