Sharing Data over Views

Published by @SoNiceInfo at 6/24/2020


Use variables to share data with multiple Views.

Update

@StateObject was introduced in WWDC20 for iOS 14.
@ObservedObject instance is recreated with view's lifecycle.
@StateObject instance is kept regardless of the state of view.

Introduction

There are three ways to use Property Wrappers.

  • @Binding the @State Variables.
  • Pass @ObservableObject for Each View.
  • Use @EnvironmentObject.
In both cases, @Binding and @Published are used to notify changes of the data to all views.

@Binding the @State Variables.

Declare the @Binding variable in the child view, pass it frome the parent view with 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()
    }
}
Pass parent's @State variable to child's @Binding variable.

Pass @ObservableObject for Each View.

Define the Model as ObservableObject.
Create an instance of @ObservedObject in the parent View.
If you declare @ObservedObject variable in a child View, pass it as an argument to the 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()
    }
}
Pass ObservableObject for each View

Use @EnvironmentObject.

If you want to use variables on all views, use @EnvironmentObject.
The point is stating that you are using an environmentObject in SceneDelegate.swift. With iOS 14, stating in WindowGroup.

iOS 13 Support

//
//  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 Support

import SwiftUI

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

Common

//
//  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()
    }
}
Use @EnvironmentObject

References

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

    I released iOS App!

    ToDo App

    Visualize Activity, Data sharing with iCloud, Dark mode supported.

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

    IP Address bookmark.

    Check and bookamrk IP address of all interfaces with geolocation.

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