Understanding of Property Wrappers in SwiftUI

Hetashree Bharadwaj
Mindful Engineering
8 min readDec 30, 2020

As we know about two amazing announcements by Apple…. SwiftUI & Combine.

SwiftUI is Apple’s new framework for writing user interfaces across all Apple platforms in a declarative and highly com-posable way.

A combine is Apple’s new unified declarative framework for processing values over time. This is a roundabout way of talking about reactive programming, of which RxSwift and ReactiveCocoa are the ambassadors.

This article is actually for features of power of SwiftUI & Combine & developers can use easily compliant with UIKit: PROPERTY WRAPPERS

So let’s get our hands dirty and try it out! 🙌🏻

Property wrappers are also introduced during the WWDC 2019. Property wrappers come with Xcode 11 in swift 5.

Brief Summary of Property Wrappers

SwiftUI relies heavily on the property wrappers to make out code easier to read, write & maintain. I think you forgot to use the “@” and “$” signs have come from — they can seem alien at first.

There are nine types of Property Wrappers.

@State, @StateObject, @Published, @ObservedObject, @EnvironmentObject, @Environment, @Binding, @GestureState, @FetchRequest

We will be going into details on each one of SwiftUI’s Property wrappers one by one.

  • Some Property wrappers allow us to achieve effectively that not possible through others, such as the way @State lets us modify property inside a struct.
  • You may apply only one property wrapper at a time, which means….

@ObservedObject @Binding var value = SomeClass() is not allowed.

  • Some property wrappers specifically required you to have done extra elsewhere, which may crash your application if that work isn’t done.
  • Even some property wrappers look similar but they all are different and it's important to use them appropriately.
  • You can create your own property wrappers if you want, but it’s not required.

@State

@State property wrapper uses in SwiftUI. @State allows us to modify the values inside the struct which is normally not be allowed because structs are value types. When you use the @State before property, we effectively more its storage out of our struct and into shared storage managed by SwiftUI. That means SwiftUI can destroy the struct & recreate the struct where it's needed without losing the state it was storing.

It should be used with simple struct types like String and Int, and generally shouldn’t be shared with the other views. If you want to share values across views, you should probably use @ObservedObject or @EnvironmentObject instead of both will ensure that all views will be refreshed when the data changes.

If you want to re-enforce the @State property then Apple recommends you mark then as private like :

@State private var name = “ ”

@StateObject

The @stateobject property is used for similar reasons @State. There’s a new member of that family: @StateObject.

Except for the @ObservableObjects. An @ObservableObjects is always a reference type of class. And informs SwiftUI whenever one of them is @Published property then it will change. Using this we can fill a nice specific gap in state management.

class Data: ObservableObject {@Published var currentValue = “a value”}struct DataView: View {@StateObject private var dataProvider = Data()var body: some View {Text(“dataProvider value: \(dataProvider.currentValue)”)}}

The DataView will create an instance of the Data. That means that DataView owns the Data. Whenever the value of the Data.currentValue changes and the dataView will render.

Internally, the swiftUI will keep updating the instance Data around whenever the swiftUI decides to discard & create the DataView for fresh render. This means that a @StateObject for any given view is initialized only once.

In Other Words, a property marked as @StateObject will keep its initially assigned ObsevedObject instance as long as the view is needed, even when the struct gets recreated by swiftUI.

The same behavior that w can see in @state, except it is applied to an ObservableObject rather than a value type as a struct.

@Published

@Published is the most useful property wrappers in SwiftUI. It is allowing to create the observable objects that automatically announce when changes occur. SwiftUI automatically observes changes & re-invokes the body property of views that rely on the data. Whenever an object property marked @Published is changed, all views using that object will be reloaded to reflect those changes.

For Example,Class Company: ObservableObject {var employee = [String]()}

It conforms to the ObservableObject protocol, which means SwiftUI’s views can watch it for changes. But its only property isn’t marked with @Published, no change announcements will ever be sent — you can add employee values to the array freely and no views will update.

If you wanted to change the announcements to be sent whenever something was added or removed from the employee, you would mark it with @Published

Class Company: ObservableObject {@Published var employee = [String]()}

You don’t need to change anything when you are using the @Published property wrapper effectively adds a will Set property observer to the employee, so changes can automatically be observed.

As you can see, @Published you need to list which properties should cause announcements, because the defaults are that changes don’t cause reload. This means you can have properties that store caches, properties for internal use, and more, and they won’t force SwiftUI to reload views when they change unless you specifically mark them with @Published.

@ObservedObject

Using the ObservedObject, you can change the stat of an external object, and be notified when something important has changed.

For Example,

class Company: ObservableObject {@Published var employee = [String] ()}struct ContentView: View {@ObservedObject var company = Company()var body: some View {// your code here}}

The Company class uses @Published so it will automatically send change announcements when employee changes and ContentView uses @ObesevedObject to watch for those announcements. Without @ObservedObject the change announcements would be sent but ignored.

First of all, if you mark with @ObservedObject must conform to the ObservableObject protocol, which intern means it must be a class rather than a struct.

And the other thing is that not all properties In an observed object cause views to refresh — you need to decide which properties should send change notifications, either using @Published or custom announcements.

@EnvironmentObject

SwiftUI’s @EnvironmentObject property wrappers allow us to create views that rely on shared data, often across an entire SwiftUI app.

For Example,

class Company: ObservableObject {@Published var emps = [String]()}

In the above example, a company class will share across the many parts of your app, you should use @EnvironmentObject. The above example conforms to @ObservedObject, which means you can use @ObservedObject or @EnvironmentObject. After that, we can create a view using @EnvironmentObject.

For Example,

struct ContentView: View {@EnvironmentObject var company: Companyvar body: some View {// your code here}}

@EnvironmentObject and @ObservedObject have some common with: Both must refer to a class that conforms to ObservableObject, both can be shared across many views, and both will update any views that are watching when significant changes happen. It specifically means “object will be provided from some outside entity, rather than being created by the current view or specifically passed in.”

@Environment

SwitUI gives us both @Environment & @EnvironmentObject property wrappers, but they both are different:

@Environment is used to work with pre-defined keys. Whereas @EnvironmentObject is used to inject arbitrary values into the environment.

If I m giving an example of @Environment than the @Environment is a great reading out of things like a core data managed object context, whether the device is on dark mode or light mode, what the size class of your view being rendered with, and more — fixed properties that come from the system.

For Example of @EnvironmentObject,

@EnvironmentObject var company: Company

There is a very minor difference between both, it’s important because of the way @EnvironmentOnbject is implemented.

When we can say that the company is a type of Company. SwiftUI will look through its environment to find an object of that type and attach it to the company property.

When we are using the @Environament the same behavior isn’t possible, because many things might share the same data type.

For Example,

@Environment(\.accessibilityReduceMotion) var reduceMotion@Environment(\.accessibilityReduceTransparency) var reduceMotion@Environment(\.accessibilityEnabled) var accessibility enabled

In the above example, all the three environment key returns boolean, so without specifying exactly which key we mean it would be impossible to read them correctly.

@Binding

@Binding is a less used property Wrapper but still, it’s important. @Binding is not the same as other property wrappers.

For Example,

struct UserView: View {@Binding var isPresented: Boolvar body: some View {Button(“Dismiss”) {self.isPresented = false}}}

In the above example, we have UserView and what will happen is for the UserView. This is exactly what @Binding for: we set a Boolean variable “isPresented” to check the user is presented or not, which will cause ContentView to hide it. The user view says that “this value will be provided from elsewhere, it will share between us.” So we can create that UserView to replace the // show the user view comment form earlier we would need to provide the value so it can be manipulated. Like…

.sheet(isPresented: $showingAddUser) {UserView(isPresented: self.$showingAddUser)}

The above code allows both ContentView & UserView to share the same Boolean value when it changes in one place it also changes in the other place.

@GestureState

SwiftUI gives us a special property wrapper that is similar to the @State but works only with gestures. It comes with the added ability that it automatically sets your property back to its initial value when the gesture ends.

SwiftUI Provides a bunch of ready tap gestures like TapGesture, DragGesture, RotationGesture, MagnificationGesture, LongPressGesture. You can use it by attaching a gesture modifier to any view.

For Example,

@GestureState var dragAmount = CGSize.zero

That has the default value of CGSize.zero, which means when the gesture ends it will return to that value automatically.

Image(“example-image”).offset(dragAmount)

Next, we would attach an offset() modifier so that our view gets moved around by whatever value is in dragAmount:

Image(“example-image”).offset(dragAmount).gesture(DragGesture().updating($dragAmount) { value, state, transaction instate = value.translation})

Finally, we would attach a gesture that is bound to our dragAmount property.

@FetchRequest

SwiftUI gives us a dedicated property wrapper for working with Core Data fetch requests, and it allows us to embed data directly into SwiftUI views without having to write extra logic.

You have to provide the least two values with the @FetchRequest. First, the entity that you want to read and any sort of descriptor to arrange the data. You can also provide a predicate to filter the data optionally.

Before using @FetchRequest you must first have injected a Core Data managed object context into the environment.

For Example,

@FetchRequest(entity: User.entity(),sortDescriptors: []) var users: FetchedResults<User>

Like above, we can add the sorting to the data, so the user will be returned to the order they were added. @FetchRequest implies @ObservedObject automatically. so if you use your data in a List, ForEach, or similar, it will automatically be refreshed when the underlying data changes.

I hope this article is helped to understand the mechanism of SwiftUI & how to use Property Wrappers. I hope you found what you read is useful and that you have learned something new from this article.

Happy Coding !! 🙂

--

--

Hetashree Bharadwaj
Mindful Engineering

Passionate App developer with expertise on Swift and Flutter, Novice writer as you might already have found out.