on
SwiftUI - Communicating with the UI
Apple provides three means of communication with different scopes.
@State
@ObservableObject
@EnvironmentObject
The @State
and @Environment
attributes belong to the SwiftUI framework.
The @ObservableObject
attribute is part of the Combine framework, Apple’s native solution to reactive programming.
Both frameworks are tightly related.
While the @State
attribute is meant to be used on a local scope at your view level (part 2 of this series), the @ObservableObject
and @EnvironmentObject
can be used through all your views.
class Navigator: ObservableObject {
enum ModalType {
case preferences, details
}
var presenting: ModalType = .preferences {
willSet {
showSheet = true
}
}
@Published var showSheet: Bool = false
}
Our navigator class conforms to the ObservableObject
protocol and contains the showSheet
property marked with the @Published
attribute.
The @Published
attribute tells the system the showSheet
property must be observed. As a result, whenever the Bool
value is toggled, changes will be applied to the views observing the navigator object. (If no property is marked as @Published
, nothing will happen since there’s nothing to observe).
Changes within an observable object will automatically be reflected to the views observing that object through the @ObservedObject
or the @EnvironmentObject
attributes.
The environment object must be attached to the hosting controller’s root view in the SceneDelegate
class.
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
var navigator = Navigator()
func scene(
_ scene: UIScene,
willConnectTo session: UISceneSession,
options connectionOptions: UIScene.ConnectionOptions) {
if let windowScene = scene as? UIWindowScene {
let window = UIWindow(windowScene: windowScene)
window.rootViewController = UIHostingController(
rootView: ContentView()
.environmentObject(navigator)
)
self.window = window
window.makeKeyAndVisible()
}
}
}
The object is initialized only once, there’s no need to pass it around afterwards, it will be accessible by all your views (with same ancestor) using the following declaration.
struct ContentView: View {
@EnvironmentObject var navigator: Navigator
var body: some View {
Text("Communicating with the UI")
}
}
While the @EnvironmentObject
attribute is convenient when all of your views need to access a specific object, @ObservedObject
may be a better fit if only few of your views need to observe that specific object.
Observed objects are initialized and passed around by reference through your views.
struct ContentView: View {
@ObservedObject var viewModel = HeadlinesViewModel(
preferences: UserPreferences()
)
var body: some View {
ContentDetailView(viewModel: viewModel)
}
}
struct ContentDetailView: View {
var viewModel: HeadlinesViewModel
var body: some View {
Text(viewModel.keyword)
}
}
@State
provides a mean of local communication.
@EnvironmentObject
and @ObservableObject
provides a mean of communication with outer objects.