データを複数のViewで共有する

Published by @SoNiceInfo at 6/24/2020


データを複数のViewで共有するには変数を使います。

更新情報

iOS 14で@StateObjectが発表されました。
@ObservedObjectのインスタンスはViewが描画されるたびに再生成されます。
@StateObjectのインスタンスはViewの状態に関わらず維持されるという特徴があります。

導入

変数を使うには以下の3つの方法があります。

  • @State変数を@Bindingする
  • @ObservableObjectをView毎に渡す
  • @EnvironmentObjectを利用する
いずれも@Binding, @Publishedを使うことでデータの変更はすべてのViewに反映されます。

@State変数を@Bindingする

変数を使いたい子Viewの中で@Bindingで宣言し、子Viewを呼び出すときにBindingして($をつけて)渡します。

//
//  ContentView.swift
//

import SwiftUI

struct ContentView: View {
    @State var name: String = ""

    var body: some View {
        VStack {
            Text("ContentView: ") + Text(name)
            InputView(name: $name)
        }
    }
}

struct InputView: View {
    @Binding var name: String

    var body: some View {
        VStack {
            TextField("Placeholder", text: $name)
                .padding()
                .border(Color.green, width: CGFloat(2))
            ResultView(name: $name)
        }
    }
}

struct ResultView: View {
    @Binding var name: String

    var body: some View {
        HStack {
            Text("ResultView: ")
            TextField("Placeholder", text: $name)
        }
    }
}
    
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
@State変数を@Bindingする結果

@ObservedObjectをView毎に渡す

ObservableObjectとしてModelを定義します。
親Viewで@ObservedObjectとしてインスタンスを作成します。
子Viewで@ObservedObjectとして宣言した場合には、Viewの引数として渡します。

//
//  ContentView.swift
//

import SwiftUI

class ViewModel: ObservableObject {
    @Published var name = ""
}

struct ContentView: View {
    @ObservedObject var vm = ViewModel()

    var body: some View {
        VStack {
            Text("ContentView: ") + Text(vm.name)
            InputView(vm: vm)
        }
    }
}

struct InputView: View {
    @ObservedObject var vm: ViewModel

    var body: some View {
        VStack {
            TextField("Placeholder", text: $vm.name)
                .padding()
                .border(Color.green, width: CGFloat(2))
            ResultView(vm: vm)
        }
    }
}

struct ResultView: View {
    @ObservedObject var vm: ViewModel

    var body: some View {
        HStack {
            Text("ResultView: ")
            TextField("Placeholder", text: $vm.name)
        }
    }
}
    
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
ObservableObjectをView毎に渡す

@EnvironmentObjectを利用する

変数をすべてのViewで使いたいときには@EnvironmentObjectを利用します。
ポイントはSceneDelegate.swiftにenvironmentObjectを使うことを明示することです。 iOS14の場合WindowGroupに明示します。

iOS 13対応

//
//  SceneDelegate.swift
//

import UIKit
import SwiftUI

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    var window: UIWindow?

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        // Create the SwiftUI view that provides the window contents.
        // 大事!!
        let contentView = ContentView().environmentObject(ViewModel())

        ...
    }
}

iOS 14対応

import SwiftUI

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

共通

//
//  ContentView.swift
//

import SwiftUI

class ViewModel: ObservableObject {
    @Published var name = ""
}

struct ContentView: View {
    @EnvironmentObject var vm : ViewModel

    var body: some View {
        VStack {
            Text("ContentView: ") + Text(vm.name)
            InputView()
        }
    }
}

struct InputView: View {
    @EnvironmentObject var vm : ViewModel
    
    var body: some View {
        VStack {
            TextField("Placeholder", text: $vm.name)
                .padding()
                .border(Color.green, width: CGFloat(2))
            ResultView()
        }
    }
}

struct ResultView: View {
    @EnvironmentObject var vm : ViewModel
    
    var body: some View {
        HStack {
            Text("ResultView: ")
            TextField("Placeholder", text: $vm.name)
        }
    }
}
    
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
@EnvironmentObjectを使う画像

参考

SwiftUIにて、一つの状態変数を複数のビューで使用する方法 | teratail

    アプリをリリースしました!

    ToDoアプリ

    iCloudを利用したデバイス間データ共有、ダークモードに対応しています。

    リリースしたToDoアプリのスクリーンショット

    IPアドレス履歴保存アプリ

    取得したIPアドレスを確認、位置情報とともに保存できます。

    リリースしたIPアドレス保存アプリのスクリーンショット