 
      Last week we started a new series of posts about the SwiftUI framework. Today I want to continue this topic by talking about Property Wrappers in SwiftUI. SwiftUI provides us with wrappers for the
@State
      
      ,
@Binding
      
      ,
@ObservedObject
      
      ,
@EnvironmentObject
      
      and
@Environment
      
      . So, let's try to understand the difference between them and when, why and which one we should use.
Property wrappers
Property Wrappers (hereinafter referred to as “property wrappers”) are described in SE-0258 . The main idea is to wrap properties with logic, which can be extracted into a separate structure for reuse in the code base.
State
@State
      
      is a wrapper that we can use to indicate the state of a
View
      
      . SwiftUI will store it in a special internal memory outside the
View
      
      structure. Only a linked
View
      
      can access it. Once the value of the
@State
      
      property changes, SwiftUI rebuilds the
View
      
      to account for state changes. Here is a simple example.
 struct ProductsView: View { let products: [Product] @State private var showFavorited: Bool = false var body: some View { List { Button( action: { self.showFavorited.toggle() }, label: { Text("Change filter") } ) ForEach(products) { product in if !self.showFavorited || product.isFavorited { Text(product.title) } } } } }
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      
      In the above example, we have a simple screen with a button and a list of products. As soon as we click on the button, it changes the value of the state property, and SwiftUI rebuilds the
View
      
      .
@Binding
@Binding
      
      provides reference access for value type. Sometimes we need to make the state of our
View
      
      accessible to his children. But we can’t just take and pass this value, because it is a value type, and Swift will pass a copy of this value. This is where the wrapping of the
@Binding
      
      property comes to the rescue.
struct FilterView: View { @Binding var showFavorited: Bool var body: some View { Toggle(isOn: $showFavorited) { Text("Change filter") } } } struct ProductsView: View { let products: [Product] @State private var showFavorited: Bool = false var body: some View { List { FilterView(showFavorited: $showFavorited) ForEach(products) { product in if !self.showFavorited || product.isFavorited { Text(product.title) } } } } }
We use
@Binding
      
      to mark the
showFavorited
      
      property inside the
FilterView
      
      . We also use the special
$
      
      character to pass the anchor link, because without
$
      
      Swift it will pass a copy of the value instead of passing the anchor link itself.
FilterView
      
      can read and write the value of the
showFavorited
      
      property in a
ProductsView
      
      , but cannot track changes using this binding. As soon as the
FilterView
      
      changes the value of the
showFavorited
      
      property, SwiftUI recreates the
ProductsView
      
      and
FilterView
      
      as its child.
@ObservedObject
@ObservedObject
      
      works similarly to
@State
      
      , but the main difference is that we can split it between several independent
View
      
      , which can subscribe and watch the changes of this object, and as soon as the changes appear,
SwiftUI
      
      rebuilds all the views associated with this object . Let's look at an example.
 import Combine final class PodcastPlayer: ObservableObject { @Published private(set) var isPlaying: Bool = false func play() { isPlaying = true } func pause() { isPlaying = false } }
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      
      Here we have the
PodcastPlayer
      
      class, which is shared by the screens of our application. Each screen should display a floating pause button when the application is playing a podcast episode.
SwiftUI
      
      tracks changes to an
ObservableObject
      
      using the
@Published
      
      wrapper, and as soon as the property marked as
@Published
      
      changes,
SwiftUI
      
      rebuilds all the
SwiftUI
      
      associated with this
PodcastPlayer
      
      . Here we use the
@ObservedObject
      
      wrapper to bind our
EpisodesView
      
      to the
PodcastPlayer
      
      class
 struct EpisodesView: View { @ObservedObject var player: PodcastPlayer 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) } } } }
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      
      @EnvironmentObject
Instead of passing an
ObservableObject
      
      through the init method of our
View
      
      , we can implicitly embed it in the
Environment
      
      our
View
      
      hierarchy. By doing this, we make it possible for all child views of the current
Environment
      
      to access this
ObservableObject
      
      .
 class SceneDelegate: UIResponder, UIWindowSceneDelegate { var window: UIWindow? func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { let window = UIWindow(frame: UIScreen.main.bounds) let episodes = [ Episode(id: 1, title: "First episode"), Episode(id: 2, title: "Second episode") ] let player = PodcastPlayer() window.rootViewController = UIHostingController( rootView: EpisodesView(episodes: episodes) .environmentObject(player) ) self.window = window window.makeKeyAndVisible() } } struct EpisodesView: View { @EnvironmentObject var player: PodcastPlayer 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) } } } }
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      
      As you can see, we must pass the
PodcastPlayer
      
      through the
environmentObject
      
      modifier of our
View
      
      . By doing this, we can easily access the
PodcastPlayer
      
      by defining it using the
@EnvironmentObject
      
      wrapper.
@EnvironmentObject
      
      uses the dynamic member search function to find an instance of the
PodcastPlayer
      
      class in
Environment
      
      , so you don’t need to pass it through the
EpisodesView
      
      init method. Environment is the right way to inject dependencies into SwiftUI .
@Environment
As we said in the previous chapter, we can transfer custom objects to the
Environment
      
      View
      
      hierarchy inside SwiftUI . But SwiftUI already has an
Environment
      
      filled with system-wide settings. We can easily access them using the
@Environment
      
      wrapper.
 struct CalendarView: 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 the
@Environment
      
      wrapper, we gain access and subscribe to changes to system-wide settings. As soon as Locale , Calendar or ColorScheme systems change, SwiftUI recreates our
CalendarView
      
      .
Conclusion
Today we talked about the Property Wrappers provided by SwiftUI .
@State
      
      ,
@Binding
      
      ,
@EnvironmentObject
      
      and
@ObservedObject
      
      play a huge role in SwiftUI development. Thanks for attention!