iTranslated by AI
Managing Model Data in SwiftUI (Official Documentation Excerpts)
I have extracted the following excerpts from the official documentation regarding model data management in SwiftUI apps.
Making Model Data Observable
To make model data observable from SwiftUI, declare the data using the ObservableObject protocol.
class Book: ObservableObject { ... }
To synchronize with SwiftUI when an object's properties change, define them with the Published attribute.
class Book: ObservableObject {
@Published var title = "Great Expectations"
}
Conversely, for properties that do not need their changes monitored, avoid adding the attribute to prevent unnecessary overhead.
class Book: ObservableObject {
@Published var title = "Great Expectations"
let identifier = UUID() // A unique identifier that never changes.
}
Monitoring Object Changes in SwiftUI Views
Since the Book class is declared as an ObservableObject, it can be monitored from SwiftUI. To make it a target for monitoring from SwiftUI, declare the variable by adding the ObservedObject attribute.
struct BookView: View {
@ObservedObject var book: Book
var body: some View {
Text(book.title)
}
}
Let's organize the terminology.
-
ObservableObject
- A protocol to make object changes observable from SwiftUI
-
ObservedObject
- An attribute added to make an instance of an object a target for monitoring from SwiftUI
-
Published
- An attribute added to synchronize property changes with SwiftUI
Monitored objects can be passed to child views. When the monitored data changes, all affected views are automatically updated. It is also possible to pass individual properties while keeping them monitored.
struct BookView: View {
@ObservedObject var book: Book
var body: some View {
BookEditView(book: book)
}
}
struct BookEditView: View {
@ObservedObject var book: Book
// ...
}
Instantiating Model Objects within a View
SwiftUI regenerates (destroys and creates) views as needed. Instances created with the StateObject attribute are retained even if the view is destroyed.
struct LibraryView: View {
@StateObject var book = Book()
var body: some View {
BookView(book: book)
}
}
As a point of caution, SwiftUI creates an instance of the object for each instance of the view. In the following code, each LibraryView creates a different (separate) Book instance.
VStack {
LibraryView()
LibraryView()
}
Sharing Objects Throughout the Entire App
By defining a monitored object in the App or Scene instance at the entry point of the app, you can make the object available throughout the entire app.
@main
struct BookReader: App {
@StateObject var library = Library()
// ...
}
When you want to pass an object used throughout the app to a child view deep in the hierarchy, you can use the environmentObject method.
@main
struct BookReader: App {
@StateObject var library = Library()
var body: some Scene {
WindowGroup {
LibraryView()
.environmentObject(library)
}
}
}
A child view that needs to use the object can access the instance by declaring a property with the EnvironmentObject attribute.
struct LibraryView: View {
@EnvironmentObject var library: Library
// ...
}
Note that when using EnvironmentObject, you must declare it in a view that is an ancestor of the view using it. Also, don't forget that you need to add it to the PreviewProvider as well.
struct LibraryView_Previews: PreviewProvider {
static var previews: some View {
LibraryView()
.environmentObject(Library())
}
}
Using Bindings
By prefixing the object name with a dollar sign ($), you can obtain a binding to the properties of ObservedObject, StateObject, or EnvironmentObject. Using a binding allows changes made to the data through SwiftUI to be reflected back in the property.
struct BookEditView: View {
@ObservedObject var book: Book
var body: some View {
TextField("Title", text: $book.title)
}
}
Discussion