visit
Hi, everyone, my name is Maksim Nechaev. I'm a Senior iOS developer at Snoonu, and also the founder of my own startup. Like many other developers, I've written a lot of apps and features on UIKit. All the global projects I've worked with have been on it. And no wonder because it is the most common set of UI elements if we talk about iOS development. Along with UIKit, we should immediately mention layout via constraint. Often, we used SnapKit to simplify this task and write more minimalistic code.
Let's take a look at ways to store data inside a SwiftUI View. After all, you're probably already thinking, how do you store data if the View is constantly being recreated and redrawn?
In total, we can divide it into three parts.
First. Let and var, the classic use of data.
Second. @State and @StateObject. Already more SwiftUI approach
Third. @Enviroment and @EnviromentObject
struct CustomView: View {
let text: String
init(text: String) {
self.text = text
}
var body: some View {
Text(text)
}
}
The only thing to keep in mind here is that usually, when we create a constant, we assume that it will not change. The situation here is that when CustomView
is changed and redrawn, the text
constant can be changed. That is, it can be "bound" to the objects being changed.
var text: String
struct CustomView: View {
@State private var text: String
var body: some View {
Text(text)
}
func changeText() {
text = "Hello, World!"
}
}
That is, if we create a structure with some local variables to which we tie local logic, we should use @State
, so that all internal Views that use this value will be updated. Everything will work correctly, but... there is always a "but". What if we want to set the value externally?
struct CustomView: View {
@State private var text: String
init(text: String) {
self.text = text
}
var body: some View {
Text(text)
}
}
Do you think it will work, and in what cases? To be fair, it will work, but only when we set the first value. When we set all subsequent values, only the first one will be displayed inside the body
.
Here, it is important to realize that @State
does not live as long as View does. That is, when we create @State
inside View, we can change it inside View as many times as we want. The view will be updated and redrawn, but the local value will be stored. But if at the same time we try to set new values for @State
through the initializer, they will be simply ignored.
What is the output here? @State
should be used only for saving and updating local data, but not those passed externally to the structure.
Now, let's talk about using StateObject. In general, we use @StateObject
to set some kind of ViewModel for our screen. That is, we pair it with a class that obeys the ObservableObject protocol. It works very similarly to @State
, with one exception: it is created after the first initialization of the structure. That is, while @State
could somehow be passed to init
to put the first value, @StateObject
won't work. It will be deleted from memory after the initializer is called and will be created again.
Let's move on to the consideration of @Environment
. This property modifier is used to access data that is passed down the view hierarchy. Unlike @StateObject
, which is used to control the state of an object within a single view, @Environment
allows you to access data that is shared by the entire application or a specific part of it.
Let's say you have some value that needs to be available in many places in your interface, such as custom settings or a design theme. Instead of passing this value through all levels of the hierarchy, you can use @Environment
for easier access.
When you mark a property in your SwiftUI view as @Environment
,SwiftUI automatically looks up the value of that property in the nearest matching context and provides it to you. If the value changes, SwiftUI also automatically updates the views that use that value. This makes @Environment
a very powerful tool for managing data in SwiftUI.
struct ContentView: View {
@Environment(\.locale) var locale: Locale
var body: some View {
Text("Current localization: \(locale.identifier)")
}
}
Now, let's break down @EnvironmentObject
. This property modifier is used to inject dependencies into SwiftUI views. Unlike @Environment
, which is designed to access system settings or values defined at the application level, @EnvironmentObject
is used to pass and use user data between views.
When you use @EnvironmentObject
, you expect the object to be provided by the parent views. This allows you to create more modular and easily scalable applications because your views do not depend on specific implementations of their dependencies. Instead, they dynamically retrieve the necessary objects from their environment.
Example usage of @EnvironmentObject
:
class UserSettings: ObservableObject {
@Published var score = 0
}
struct ContentView: View {
@EnvironmentObject var settings: UserSettings
var body: some View {
Text("Score: \(settings.score)")
}
}
Initialization
)State Update
)After initialization, SwiftUI watches for changes in data that may affect your view. This data can include @State
, @Binding
, @ObservedObject
, @EnvironmentObject
, and @Environment
. When one of these values changes, SwiftUI starts the process of updating the view.
Body Computation
)In this step, SwiftUI calls the body
property of your view. body
is a computed property, and every time SwiftUI detects a change that affects the view, it recomputes body
. This process allows SwiftUI to determine which parts of the UI need to be updated.
Rendering & Layout
)View Activation
)Deinitialization
)body
.