Demonstrating the use cases of Property Wrappers in SwiftUI. Let us take a deep dive into property wrappers in SwiftUI in this article:
Before heading to the differences between Property Wrappers and Property Observers, we must understand the key concepts of properties available in Swift programming language.
What are properties?
Properties associate values with a particular class, structure, or enumeration.
Stored Properties
Properties either be a constant (let keyword) or variables stored (var keyword).
struct Person {
let name: String
var age: Int
}
var person = Person(name: "Saba", age: 32) person.age = 33
This is the default behaviour of the Stored properties with Structure (Value types). If the instance of structure is constant, then its properties will be constant.
In the case of reference-type - classes, we can modify their properties even if the instance is marked as constant.
We cannot use Stored property in Protocol extensions.
Computed Properties
We can define a computed property with classes, structures, and enums which don’t store a value. Instead, they provide a getter and an optional setter.
For example: I will add the computed properties such as getter and getter and setter to the struct — Person.
var fullName: String {
return "\(name) \(initial)"
}
var isAdult: Bool {
get {
return age >= 18
}
set {
if newValue {
age = 18
} else {
age = 0
}
}
}
print(person.fullName) // Saba R
print(person.isAdult) // true
person.age = 17
print(person.isAdult) // false
Lazy Properties
Property whose initial value isn’t calculated until the first time it’s used.
In simple words, it is beneficial when you want to delay the initialization of the property until it is actually needed. It improves the performance.
For example: I will add the lazy stored property to the struct — Person.
lazy var lazyProperty: String = {
print("Performing Lazy Operation")
return "Result of lazyProperty"
}()
//
print(person.lazyProperty) // Performing Lazy Operation
// Result of lazyProperty
print(person.lazyProperty)
// Result of lazyProperty
Downsides of using lazy var:
Property Wrappers
A property wrapper adds a layer of separation between the code that manages how a property is stored and the code that defines a property.
I would like to cover the property wrapper in Swift Programming.
@propertyWrapper
struct SomeStruct {
private var value = 2
var wrappedValue: Int {
get { return value }
set { value = min(newValue, 7)}
}
}
struct SomeCuboid {
@SomeStruct var height: Int
@SomeStruct var length: Int
@SomeStruct var width: Int
}
var cuboid = SomeCuboid()
print(cuboid.length) // 2
cuboid.length = 20
print(cuboid.length) // 7
A property wrapper can expose additional functionality by using $. Hereby I will add the following SomeStruct.
private(set) var projectedValue: Bool
init () {
self.value = 0
self.projectedValue = false
}
print(cuboid.$length)
print(cuboid.length)
SwiftUI
SwiftUI offers Declarative programming for User Interface design.
In SwiftUI, we have a list of property wrappers. I would like to cover some of the wrappers in this article.
Persistent Storage
State
A property wrapper type that can read and write a value managed by SwiftUI.
Use state as the single source of truth for a given value type that you store in a view hierarchy.
struct MusicPlayer: View {
@State private var isPlaying: Bool = false
var body: some View {
}
}
When should we use @State?
Bindable
A property wrapper type that supports creating bindings to the mutable properties of observable objects.
Introduced in iOS 17.0
@dynamicMemberLookup @propertyWrapper
struct Bindable<Value>
When to use @Bindable?
Binding
A property wrapper type that can read and write a value owned by a source of truth.
Create a two-way connection between a property that stores data and a view that displays and changes data.
We can share access to the state with bindings.
struct PlayMusicButton: View {
@Binding var isPlaying: Bool = false
var body: some View {
Button(action: {
self.isPlaying.toggle()
}) {
Image(systemName: isPlaying ? "pause.circle" : "play.circle")
}
}
}
Please find the Migrating from the Observable Object protocol to the Observable macro. Observable macro has been introduced in iOS 17. Using the Observable macro eliminates the need for the ObservedObject and Published property wrappers for observable properties.
Binding vs Bindable: To create bindings to properties of a type that conforms to the Observable protocol, use the Bindable property wrapper.
If your property doesn’t require observation, use the ObservationIgnored() macro.
Bindable works only with class whereas Binding works well with the value types.
For more details check this link
@ObservationIgnored var donotTrack = false
StateObject
A property wrapper type that instantiates an observable object.
@frozen @propertyWrapper
struct StateObject<ObjectType> where ObjectType : ObservableObject
Single source of truth for a reference type that is stored in the view hierarchy. We can declare and provide an initial value that conforms to the ObserevableObject protocol.
When to use @StateObject?
ObservedObject
A property wrapper type that subscribes to an observable object and invalidates a view whenever the observable object changes.
@propertyWrapper @frozen
struct ObservedObject<ObjectType> where ObjectType : ObservableObject
When to use @ObservedObject?
EnvironmentObject
A property wrapper type for an observable object that a parent or ancestor view supplies.
@frozen @propertyWrapper
struct EnvironmentObject<ObjectType> where ObjectType : ObservableObject
An environment object invalidates the current view whenever the observable object conforms to the ObservableObject changes.
environmentObject(_:) modifier
If the observable object conforms to the Observable protocol, use Environment instead of EnvironmentObject.
Environment
A property wrapper that reads a value from a view’s environment.
@frozen @propertyWrapper
struct Environment<Value>
Example Usage:
@Environment(\.colorScheme) var colorScheme: ColorScheme
struct NestedViews: View {
@Environment(Settings.self) private var settings
var body: some View {
ZStack {
Color.accentColor.ignoresSafeArea(.all)
}
}
}
In the WWDC23, Apple displayed the following flow chart — Discover Observation in SwiftUI
AppStorage
A property wrapper type that reflects a value from UserDefaults and invalidates a view on a change in value in that user default.
@frozen @propertyWrapper
struct AppStorage<Value>
Used to access UserDefaults from multiple views in a view hierarchy
struct AppStorageView: View {
@AppStorage("preferDark") var preferMode: Bool = false
var body: some View {
ZStack {
Color(preferMode ? .black : .white).ignoresSafeArea(edges: .all)
VStack {
Text("@AppStorage with @Binding")
.font(.title)
Text("Used to access UserDefaults from multiple views in a view hierachy")
.font(.subheadline)
ShapeView(preferMode: $preferMode)
SetView(preferMode: $preferMode)
}
}
.animation(.easeIn, value: preferMode)
}
}
SceneStorage
A property wrapper type that reads and writes to persisted, per-scene storage.
@frozen @propertyWrapper
struct SceneStorage<Value>
Use SceneStorage when you need automatic state restoration of the value. Similar to State.
Used to save data persistently for each scene.
struct SceneStorageView: View {
@SceneStorage("currentTime") var currentTime: Double?
var body: some View {
VStack {
Text("Button was clicked on \(dateString)")
Button("Click Here") {
currentTime = Date().timeIntervalSince1970
}
Spacer()
}
}
var dateString: String {
if let currentTimeStamp = currentTime {
return Date(timeIntervalSince1970: currentTimeStamp).formatted()
} else {
return "Never"
}
}
}
FetchRequest
A property wrapper type that retrieves entities from a Core Data persistent store.
@MainActor @propertyWrapper
struct FetchRequest<Result> where Result : NSFetchRequestResult
Use FetchRequest whenever want to fetch data by interacting with the CoreData store directly to the view.
Property Observers
Observe and respond to changes in a property’s value. Called every time a property’s value is set, even if the new value is the same as the current value.
Observers on a property are as follows:
class Temperature {
var celsius: Double = 0.0 {
willSet(newCelsius) {
print("about to change to \(newCelsius) degrees")
}
didSet {
if celsius > oldValue {
print("Increased from \(celsius - oldValue) degrees")
} else {
print("Reduced from \(oldValue - celsius) degrees")
}
}
}
}
let temperature = Temperature()
temperature.celsius = 36.1
//About to change to 36.1 degrees
//Increased from 36.1 degrees
temperature.celsius = 38
//About to change to 38.0 degrees
//Increased from 1.8999999999999986 degrees
temperature.celsius = 37.2
//About to change to 37.2 degrees
//Reduced from 0.7999999999999972 degrees
The entire project can be found here
This is a free third party commenting service we are using for you, which needs you to sign in to post a comment, but the good bit is you can stay anonymous while commenting.