Property Wrappers (Part II)
Property Wrappers (Part II)
@Binding,@Environment,@EnvironmentObject,@State,@Published,@ObservedObject
Integrating Combine
- Confirm to ObservableObject protocol for your custom object so that your data can be bound to a view.
- By adding @Binding — Use a binding to create a two-way connection between a view and its underlying model.
- By adding @Environment(property wrapper) we canpass custom objects into the Environment of a View hierarchy inside SwiftUI.
-
By adding
@EnvironmentObject
A dynamic view property that uses a bindable object supplied by an ancestor view to invalidate the current view whenever the bindable object changes. -
By adding
@Published
(property wrapper) we can add a Publisher to any property, for example:@Published var password: String = ""
-
Using
targetAction
you can assign new values to the@Published
value:password = sender.text ?? ""
- CombineLatest — combines the most recently emitted values
-
using
map
you can modify received values and change Publisher's type -
eraseToAnyPublisher()
- returns an AnyPublisher of the given type and error -
debounce(for:scheduler:)
- lets you specify a window by which you’d like to receive values and not receive them faster than that. It is useful to reduce number of requests when filtering data using text fields -
removeDuplicates()
- Publishes only elements that don’t match the previous element
ObservableObject
protocol ObservableObject : AnyObject
ObservableObject is a protocol that’s part of the Combine framework. A observable object is a custom object for your data that can be bound to a view from storage in SwiftUI’s environment. SwiftUI watches for any changes to observable objects that could affect a view, and displays the correct version of the view after a change.
Declare a new model type that conforms to the
ObservableObject
protocol from the Combine framework. SwiftUI subscribes to your
observable object, and updates any views that need refreshing
when the data changes.
Assume we have a
UserData
class which conforms to ObservableObject Protocol, it has name
& age variable. We have added the
@Published
attribute to these property in the model. An observable object
needs to publish any changes to its data, so that its
subscribers can pick up the change.
class
UserData: ObservableObject {
@Published var name: String
@Published var age: Int
init(name: String, age: Int) {
self.name = name
self.age = age
}
func haveBirthday() -> Int {
age += 1
return age
}
}
Now we need to adopt the Person Object in our views.
@EnvironmentObject var user: UserData
This
user
property
gets its value automatically, as long as the
environmentObject(_:)
modifier has been applied to a parent.
@EnvironmentObject :-
Instead of passing ObservableObject via init method of our View we can implicitly inject it into Environment of our View hierarchy. By doing this, we create the opportunity for all child Views of current Environment access this ObservableObject.
As you can see, we have to pass UserData object via environmentObject modifier of our View. By doing this, we can easily access UserData by defining it with @EnvironmentObject Property Wrapper. @EnvironmentObject uses dynamic member lookup feature to find UserData class instance in the Environment, that’s why you don’t need to pass it via init method of View Class. The Environment is the right way of Dependency Injection in SwiftUI. It works like magic.
@Published :-
SwiftUI tracks the changes on ObservableObject with the help of @Published property wrapper, and as soon as a property marked as @Published changes SwiftUI rebuild all Views bound to that UserData Object.
@propertyWrapper struct Published<Value>
Publishing a property with the
@Published
attribute creates a publisher of this type. You access the
publisher with the
$
operator.
@Published allows us to transform this property into a stream of
its successive values.
Suppose we have a ViewController, we listen for UITextField updates, in the callback method we are updating a string variable named “username”. We set the value of a String property named “username” annotated with @Published. The @Published property wrapper makes $username be a Publisher that we can subscribe to.
Assume we have a label which need to update as user enters text
in UITextField, we can use $username to bind with label, so that
it can be updated as the username strings update.This first
thing you might notice is the dollar sign in front of
username. It allows you to access the wrapped
Publisher
value.
Important: The
@Published
attribute is class-constrained. Use it with properties of
classes, not with non-class types like structures.
@State,@Binding :-
@State is a Property Wrapper which we can use to mark View’s state. SwiftUI will store it in special internal memory outside of View struct. Only the related View can access it. As soon as the value of @State property changes SwiftUI rebuilds View to respect state changes.
@Binding provides reference like access for a value type. Sometimes we need to make the state of our View accessible for its children. But we can’t simply pass that value because it is a value type and Swift will pass the copy of that value. And this is where we can use @Binding Property Wrapper.
struct FavouriteView: View {
@Binding var showFavorited: Bool
var body: some View {
Toggle(isOn: $showFavorited) {
Text("Change favourite")
}
}
}
struct UserView: View {
let users: [User]
@State private var showFavorited: Bool = false
var body: some View {
List {
FavouriteView(showFavorited: $showFavorited)
ForEach(users) { user in
if !self.showFavorited || user.isFavorited {
Text(user.title)
}
}
}
}
}
We use @Binding to mark
showFavorited property
inside the
FavouriteView
. We also use $ to
pass a binding reference, because without
$
Swift will pass a copy
of the value instead of passing bindable reference.
FavouriteView
can read and write the value of
UserView’s
showFavorited property,
but it can’t observe the changes using this binding. As soon as
FavouriteView
changes value of
showFavorited property,
SwiftUI will recreate
the UserView and
FavouriteView as its
child.
@ObservedObject
@ObservedObject work very similarly to @State Property Wrapper, but the main difference is that we can share it between multiple independent Views which can subscribe and observe changes on that object, and as soon as changes appear SwiftUI rebuilds all Views bound to this object. Let’s take a look at an example.
import Combine
final class MusicPlayer: ObservableObject {
@Published private(set) var isPlaying: Bool = false
func play() {
isPlaying = true
}
func pause() {
isPlaying = false
}
}
Here we have MusicPlayer class which we share between the screens of our app. Every screen has to show floating pause button in the case when the app is playing a TV series episode. SwiftUI tracks the changes on ObservableObject with the help of @Published property wrapper, and as soon as a property marked as @Published changes SwiftUI rebuild all Views bound to that MusicPlayer object. Here we use @ObservedObject Property Wrapper to bind our TVSeriesView to MusicPlayer class
struct TVSeriesView: View {
@ObservedObject var player: MusicPlayer
let episodes: [Episode]
var body: some View {
List {
Button(
action: {
if self.player.isPlaying {
self.player.pause()
} else {
self.player.play()
}
}, label: {
Text(player.isPlaying ? "Pause": "Play")
}
)
ForEach(episodes) { episode in
Text(episode.title)
}
}
}
}
@Environment
We can pass custom objects into the Environment of a View hierarchy inside SwiftUI. But SwiftUI already has an Environment populated with system-wide settings. We can easily access them with @Environment Property Wrapper.
struct MyCalendarView: View {
@Environment(\.calendar) var calendar: Calendar
@Environment(\.locale) var locale: Locale
@Environment(\.colorScheme) var colorScheme: ColorScheme
var body: some View {
return Text(locale.identifier)
}
}
By marking our properties with @Environment Property Wrapper, we access and subscribe to changes of system-wide settings. As soon as Locale, Calendar or ColorScheme of the system change, SwiftUI recreates our MyCalendarView.
Hope this article is useful for iOS developers, Please ❤️ to recommend this post to others 😊. Let me know your feedback. :)
Reference: