Are you building an app with SwiftUI and wondering how to manage your app's state? Data binding is a powerful tool that can help you build dynamic and responsive interfaces.
In this tutorial, we'll explore how to use @State
, @ObservedObject
, and @EnvironmentObject
.
What is data binding in SwiftUI?
Data binding connects UI element to a piece of data in your app. When the data changes, the UI element automatically updates to reflect the new value, and when the user interacts with the element, the data updates to reflect the new input.
SwiftUI provides several tools for data binding: @State
, @ObservedObject
, and @EnvironmentObject
. These tools allow you to bind values, objects, and even global objects to your user interface.
How to use @State to bind a simple value to your user interface
@State
is a property wrapper that allows you to bind a simple value, like a string or an integer, to your user interface.
@State
can be used to bind value-type objects only. So, any struct
also can be binded using @State
.To use @State
, you first define a property with the @State
wrapper, and then use the property in your user interface as a usual. For example, here's how you might use @State
to bind a string to a text field:
struct ContentView: View {
@State private var name: String = ""
var body: some View {
VStack {
TextField("Enter your name", text: $name)
Text("Hello, \(name)!")
}
}
}
You may notice that $name
is used. It allows to access projectedValue
of the wrapper. In case of @State
it is Binding<Type>
.
Now, whenever name is changed, the UI updates automatically. And when the user modifies the text field, variable data gets updated too.
Using @Binding
@Binding
is used when you want to bind a value or object that is owned by a different view.
To use @Binding
, you first define a property with the @Binding
wrapper, and then pass the binding to another view as an argument. The other view can then use the binding to read and write the data from the original view.
struct CustomTextField: View {
@Binding var text: String
var body: some View {
HStack {
Image(systemName: "person.circle")
TextField("Enter your name", text: $text)
}
.padding()
}
}
struct ContentView: View {
@State private var name: String = ""
var body: some View {
VStack {
CustomTextField(text: $name)
Text("Hello, \(name)!")
}
}
}
You also can pass binding in init
using direct access to property wrapper through underscore.
struct CustomTextField: View {
@Binding var text: String
init(text: Binding<String>) {
self._text = text
}
var body: some View {
HStack {
Image(systemName: "person.circle")
TextField("Enter your name", text: $text)
}
.padding()
}
}
@Binding
as a channel that gets value from the source and sets value to the source. It does not own an object.Therefore, @Binding
is great for the view decomposition as it allows to inject dependencies to subviews.
Read more about modular app architecture with SwiftUI in my previous post:
How to use @ObservedObject to bind a class to your user interface
@ObservedObject
allows you to bind a class to your user interface. The class must conform to the ObservableObject
protocol and use the @Published
property wrapper for any properties that you want to bind to your user interface. When the object's @Published
properties change, the user interface updates.
Here's an example of how you might use @ObservedObject
to bind a User
object to a form:
class User: ObservableObject {
@Published var name: String = ""
@Published var email: String = ""
var someUntrackedValue = ""
}
struct ContentView: View {
@ObservedObject private var user = User()
var body: some View {
VStack {
TextField("Enter your name", text: $user.name)
TextField("Enter your email", text: $user.email)
Text("Hello, \(user.name)!")
}
}
}
In this example, the user
property is bound to the text fields using the $user.name
and $user.email
syntax. When the user types in the text fields, the name
and email
properties of the User
object update to reflect the new input, and the Text
view updates to show the new value.
ObservableObject
, then changes inside it will not be propagated.How to use @EnvironmentObject to bind a global object to your user interface
EnvironmentObject allows you to bind a global object. The object must conform to the ObservableObject
protocol the same way as with @ObservedObject
.