As your books are updated, you will want to download newer versions of that book.
Follow this video to learn how to log in, where to look, and how to know which version of the book you want to download.
SwiftUI’s @Environment
is a powerful tool that allows you to share data (with an observable object) between multiple views.
It is particularly useful when you have a complex view hierarchy and need to share the same observable object between views that are not directly connected.
You could pass your observable object through view initializers, from parent to child to child to child, etc. This can be a lot of work.
Using environment objects allows you attach an object (instance of an observable object) to a parent view.
Any descendants (child views, sub views) of that parent view now have access to that object.
(This is a page from the book “SwiftUI Essentials: Architecting Scalable & Maintainable Apps”)
To use Environment, you first need to create an Observable class that has the @Observable
attribute.
This class will hold the data that you want to share between your views.
@Observable
class DeveloperOO {
var name: String = "Awesome developer"
}
Next, you need to create an instance of this class and pass it to your top-level view using the .environment(_:)
modifier.
var body: some View {
ContentView()
.environment(DeveloperOO())
}
In this example, all subviews within ContentView
can access the DeveloperOO
observable object.
Another common example is to add an observable object to a container view, such as a NavigationStack
or TabView
so any descendant views can access it.
(This is a page from the book “SwiftUI Essentials: Architecting Scalable & Maintainable Apps”)
Now, any view in your hierarchy can access this object by declaring an @Environment
property.
struct DetailView: View {
@Environment(DeveloperOO.self) var devInfo
var body: some View {
Text("Hello, \(nameInfo.name)!")
}
}
That’s it!
Note: The type (
DeveloperOO.self
) is what makes this all work.As long as the type is the same, your view will find the matching object in the environment.
In a previous example, an observable object was added to the TabView’s environment.
This is how one of the tab views can access that observable object:
(This is a page from the book “SwiftUI Essentials: Architecting Scalable & Maintainable Apps”)
You can now access and modify the shared data from any view in your hierarchy.
Imagine this view hierarchy:
View 1 has a NavigationStack with a NavigationLink to View 2
View 2 has a NavigationLink to View 3
View 3
View 1 and View 3 both use the same observable object.
Here is View 1:
struct View1: View {
@State var oo = DeveloperOO()
var body: some View {
NavigationStack {
NavigationLink("Navigate to View 2") {
View2()
}
.navigationTitle("Parent View")
}
}
}
You might think you add it to View2 so it can be passed on to View3:
NavigationLink("Navigate to View 2") {
View2()
.environment(oo)
}
❌ This is incorrect!
The observable object would just be in View2's environment which does not include View3. So View3 will not be able to access it.
✅ The correct solution would be to put the environment object on the navigation stack.
struct View1: View {
@State var oo = DeveloperOO()
var body: some View {
NavigationStack {
NavigationLink("Navigate to View 2") {
View2()
.environment(oo) // ❌
}
.navigationTitle("Parent View")
}
.environment(oo) // ✅
.font(.largeTitle)
}
}
All views you navigate to within a navigation stack are considered within the navigation stack's view hierarchy.
Now, every view within the navigation stack can potentially access the observable object through the environment if needed.
To preview a view that uses the @Environment
, you will have to add the .environment(_:)
modifier to the previewed view like this:
#Preview("Child View") {
TabViewOne()
.environment(DeveloperInfo())
}
Use the environment
modifier to add the observable to the view or else you will get a preview error.
The parent view doesn't need this because it is the view that sets the environment object.
The @Environment
property wrapper is a powerful tool for sharing data between different views in your SwiftUI application.
By passing an object down the view hierarchy using the environment(_:)
modifier, you can access it from any view without having to pass it explicitly as a parameter.
You use @Environment when:
You want to create a global place for views to access data
You want to create two-way bindings between the data and the UI
You want multiple views to simultaneously get updated when one observable object is updated
You can also learn more from my book SwiftUI Essentials: Architecting Scalable & Maintainable Apps:
Learn architecture and data in SwiftUI with "SwiftUI Essentials”.
Create UI with SwiftUI Views Mastery (beginner)
Architect your app using SwiftUI Essentials (beginner)
Improve your app's UX with SwiftUI Animations Mastery (junior)
Save & sync your app's data with Core Data Mastery -or- SwiftData Mastery (junior)
React to and manipulate data using Combine Mastery in SwiftUI (advanced)
Recently in my local Apple development Slack group, someone asked:
In an
HStack
with two elements, how can I center-align one element and right-align the other?
"End Date" should be center-aligned
The Done button should stay on the trailing side.
Currently the "End Date" is not centered within the screen's width.
Here is the code he started with:
HStack {
Spacer()
Text("End Date")
.font(.title3.weight(.semibold))
Spacer()
Button("Done") { }
.buttonStyle(.borderedProminent)
}
.padding()
Immediately, you might think this is an alignment problem.
But actually, the solution is with layers (and then alignment).
I find it easier to align individual views within their own spaces. I rarely use the Spacer()
view.
So I would put the text and button on their own layer and align them separately.
When working with layers in SwiftUI, you should think in 3 ways:
ZStack
Overlay
Background
The above layout can be solved using ANY of the 3 ways above.
Let's take a look:
ZStack(alignment: .trailing) {
Text("End Date")
.font(.title3.weight(.semibold))
.frame(maxWidth: .infinity)
Button("Done") { }
.buttonStyle(.borderedProminent)
}
We have the text stretch to the full width of the screen and within the frame, the text will be centered.
Since the alignment is set on the ZStack, any view not filling the total width will be trailing-aligned, such as the button.
Text("End Date")
.font(.title3.weight(.semibold))
.frame(maxWidth: .infinity)
.overlay(alignment: .trailing) {
Button("Done") { }
.buttonStyle(.borderedProminent)
}
Again, the text is stretched full width.
The overlay modifier sets the alignment to trailing so anything within it will be aligned.
Text("End Date")
.font(.title3.weight(.semibold))
.frame(maxWidth: .infinity)
.background(alignment: .trailing) {
Button("Done") { }
.buttonStyle(.borderedProminent)
}
Similar to overlay, but uses the background modifier instead.
The button will still work if it's not covered up.
Here is the result:
As you can see, all three solutions solve the problem.
But there is one thing to note:
Views within the overlay and background can extend beyond the parent view.
When you add .border(.red)
to each of the solutions, you can see this:
Notice the buttons go beyond the border for the overlay and background solutions.
This could potentially interfere with your layout. So it is something to keep in mind when using them.
A good rule to follow is:
Your parent view should take up more space than your background and overlay views.
This will help prevent potential layout problems.
Unlock your SwiftUI potential and take your app-building skills to the next level with the free guide, “SwiftUI Views Quick Start”.
Create UI with SwiftUI Views Mastery (beginner)
Architect your app using Working with Data in SwiftUI (beginner)
Improve your app's UX with SwiftUI Animations Mastery (junior)
Save & sync your app's data with Core Data Mastery -or- SwiftData Mastery (junior)
React to and manipulate data using Combine Mastery in SwiftUI (advanced)
In this blog post, we’re going to learn 3 ways to add background colors to a SwiftUI screen.
There are many ways to create layers in SwiftUI:
Background modifier
Overlay modifier
ZStack view
Each of these items can be used to create background colors.
Let’s look at examples of each.
Here’s an example of how to add a background color to a SwiftUI view using the background modifier:
struct Background_Color: View {
var body: some View {
VStack {
Text("Using Background Color")
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background {
Color.teal.opacity(0.3)
.ignoresSafeArea()
}
}
}
If you are using the background modifier then your root view should take up the whole screen (that’s why you see max width and height on the VStack).
The ignoresSafeArea() modifier is used on the color so it extends into the safe areas.
The safe area is where your UI can be presented without device elements covering them up, such as a notch, island, or the home indicator at the bottom of the screen.
Safe Area — Page from SwiftUI Views Mastery book by Big Mountain Studio
Here’s an example of how to add a background color to a SwiftUI view using the overlay modifier:
struct Overlay_BackgroundColor: View {
var body: some View {
Color.teal.opacity(0.3)
.ignoresSafeArea()
.overlay {
VStack {
Text("Using Overlay")
}
}
}
}
Color views are “push-out” views, meaning they will use up as much space as they can. So you don’t have to add maxHeight and maxWidth to make them stretch out.
Your main screen content will go directly into the overlay modifier.
Here’s an example of how to add a background color to a SwiftUI view using the ZStack view:
struct ZStack_BackgroundColor: View {
var body: some View {
ZStack {
Color.teal.opacity(0.3)
.ignoresSafeArea()
Text("Using ZStack")
}
}
}
The ZStack layers the containing views within it. The first view (the color in this case) is the furthest back.
This is my preferred method of adding a background color to a view in SwiftUI. It’s very clean, with less indenting.
Here's an example of how to add a background color to a SwiftUI List:
struct Background_List: View {
private var stringArray = ["Lemuel", "Mark", "Chris", "Chase", "Adam", "Rodrigo"]
var body: some View {
List(stringArray, id: \.self) { string in
Text(string)
}
.background(Color.teal.opacity(0.3))
.scrollContentBackground(.hidden)
.font(.title)
}
}
If you want the background to show behind the rows too, then you can use the listRowBackground
modifier:
struct Background_List: View {
private var stringArray = ["Lemuel", "Mark", "Chris", "Chase", "Adam", "Rodrigo"]
var body: some View {
List(stringArray, id: \.self) { string in
Text(string)
.listRowBackground(Color.clear)
.listRowSeparator(.hidden)
}
.background(BackgroundView())
.scrollContentBackground(.hidden)
.font(.title)
}
}
Since the List pushes out to take up the whole screen, you don't need to use a ZStack
here.
What makes this work is the scrollContentBackground
modifier on the List. This will also work for any ScrollView
too.
You can create a color set in your Xcode Asset Library (Assets.xcassets file):
Then you reference the name of this color ("BackgroundColor") in code:
struct Background_NamedColor: View {
var body: some View {
ZStack {
Color("BackgroundColor")
.ignoresSafeArea()
VStack {
Text("Using Named Color")
}
.font(.title)
}
}
}
It may be simpler to have your background defined in just one view that you can use everywhere.
Here's an example of how you can do that:
struct Background_ExtractedView: View {
var body: some View {
ZStack {
BackgroundView()
VStack {
Text("Using Extracted View")
}
.font(.title)
}
}
}
struct BackgroundView: View {
var body: some View {
Color.teal
.opacity(0.3)
.ignoresSafeArea()
}
}
If you change your mind later, you can replace the content of the BackgroundView
with a different background, such as a blurred image:
struct BackgroundView: View {
var body: some View {
Image("mountain")
.resizable()
.scaledToFill()
.ignoresSafeArea()
.opacity(0.5)
.overlay(.thinMaterial)
}
}
Or a gradient:
struct BackgroundView: View {
var body: some View {
LinearGradient(colors: [Color.teal, Color.indigo],
startPoint: .topLeading,
endPoint: .bottomTrailing)
.opacity(0.5)
.ignoresSafeArea()
}
}
The BackgroundView
can be in a separate file where your other common, reusable views are.
Once used throughout your app, you can just make changes in one place to affect all backgrounds.
I usually add some opacity to the background so light and dark modes show better.
Unlock your SwiftUI potential and take your app-building skills to the next level with the free guide, “SwiftUI Views Quick Start”.
Create UI with SwiftUI Views Mastery (beginner)
Architect your app using Working with Data in SwiftUI (beginner)
Improve your app's UX with SwiftUI Animations Mastery (junior)
Save & sync your app's data with Core Data Mastery -or- SwiftData Mastery (junior)
React to and manipulate data using Combine Mastery in SwiftUI (advanced)
Hi! I am working through this book. I am currently on 'Fetch - Observable Object'. I am getting an error when I try to compile. Can anyone offer assistance?
Error:
Variable 'self.oo' used before being initialized
I even copied and pasted what was offered in the Project -> OO_Fetch
Hi Mark,
I am working with the latest 'SwiftData Mastery in SwiftUI' book. Page 356 and Page 357.
@State private var oo: OO_FetchOO init(modelContext: ModelContext) { self.oo = OO_FetchOO(modelContext: modelContext)”
Excerpt From
SwiftData Mastery in SwiftUI
Mark Moeykens
This material may be protected by copyright.