Better Programming

Advice for programmers.

Follow publication

The Best Way to Use Environment Objects in SwiftUI

Mohammed Imthathullah
Better Programming
Published in
4 min readApr 21, 2020
We should minimize our impact on the environment, even in our SwiftUI app. Photo by Joren on Unsplash

The data flow in SwiftUI apps is different from what we have been working on for years with any other earlier imperative UI frameworks from Apple such as UIKit or AppKit.

In SwiftUI, we can just declare views and it always stays in sync with the data.

The properties in your views can be wrapped with State, Binding, or Observed property wrappers. Whenever the value of the properties provided by these wrappers changes, the view is redrawn accordingly.

Property wrappers to share data between SwiftUI views. Slide 82 of this presentation updated.

Ideally, views should be small and have private State properties, with one or few Binding or Observed properties. Because, we would not want to re-draw your view often.

But then, Apple provided us with EnvironmentObject. As the name goes, an environment object can let us know of something that happens anywhere in the app to a lot of unrelated views.

How EnvironmentObject shares data across views PC: Apple

Apple provided us with a long list of environment values.

In the good old times, we got to know about these changes via ViewController life cycle methods, like when a user changes theme (yeah, triatCollectionDidChange), and NotificationCenter, like when a user changes the device orientation.

These environment values can be injected into any view easily.

@Environment(\.presentationMode) var presentationMode

The above property in a view can help to know the current presentation mode or to dismiss the view manually.

We can also create our own classes conforming to ObservableObject and then make them EnvironmentObjects.

It has been touted as the next level of dependency injection. We can inject whatever you want, wherever you want. Sounds familiar? First, let us see what the Apple documentation says.

“An environment object invalidates the current view whenever the observable object changes. If you declare a property as an environment object, be sure to set a corresponding model object on an ancestor view by calling its environmentObject(_:) method.”

Does anything smell bad? What does “be sure to set a corresponding model object on an ancestor view” mean? A runtime check? What if the check fails? We’ll find out in a minute.

OK, here is a simple example for using EnvironmentObject from a Hacking with Swift tutorial.

We have two views, one to change the score and another to display the score. Both views read/write the score to the same environment object.

For this to work properly, we have to inject the environment object in the root view of the hosting controller in the scene delegate as mentioned in the tutorial.

But what will happen if we don’t inject the environment object in the root view?

Fatal error: No ObservableObject of type UserSettings found. A View.environmentObject(_:) for UserSettings may be missing as an ancestor of this view.

No prizes for guessing correctly. The app crashes with the above error. Why so?

The environment object could be nothing but an implicitly unwrapped optional, we do not know its implementation details.

But the error message closely resembles the errors thrown when you force unwrap a nil object. Also, it is quite hard to think of any other implementation for achieving the same functionality.

Now back to optionals. There has been an argument since time immemorial in the Apple developer community whether it is a good practice to use these implicitly unwrapped optional properties.

Without getting into that debate, let us find a way to ensure that these environment objects are initialized when the app is launched and ready to use in any view without crashing.

Remember, we talked about the ability to use Environment values in any view of your app. Did it sound familiar to you? The good old Singleton.

Here, we are making UserSettings a Singleton class, i.e. It is initialized on app launch and ready to use everywhere.

We have already conformed it to the ObservableObject protocol and so we can use the ObserveredObject property wrapper in the views for the UserSettings property instead of EnvironmentObject.

This ensures that whenever there are changes to the score in the shared UserSettings object, all the views that are observing it will be notified and each view is updated accordingly.

This ObservableSingleton behaves exactly like the EnvironmentObject except that it won’t crash if you fail to inject. Actually, the former provides you with compile-time safety. The code that compiles properly, should run properly — no unexpected runtime errors.

So, if compile-time safety is your top priority or you are working on a large SwiftUI project with many developers (this may not happen in the near future), Singleton classes conforming to the Observable protocol might serve you better than EnvironmentObjects.

But, do you know what the best way is to use EnvironmentObject?

Do not use it or at least reduce the usage of it. Because, both the EnvironmentObject and the ObservableSingleton stay in the memory (RAM) all the time when your app is running.

Hence, design your app in such a way that it requires very few properties to be shared across multiple views and make sure that these properties are primitive types and lightweight.

If you can design your app without any such properties, it is better for you and the app.

Happy Swifting!

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

Mohammed Imthathullah
Mohammed Imthathullah

Written by Mohammed Imthathullah

Aspiring Author. Mostly writes code and sometimes articles.

Write a response