3 ways to share state in SwiftUI that you NEED to know 🚀💯
Tags: swift, swiftui, beginners, tutorial
Published: 2022-08-29 20:25:23

It is common in many SwiftUI architecture patterns to separate your logic from your UI into small `ObservableObjects` as follows:

```swift
import Foundation
import SwiftUI

struct ContentView: View {
	@StateObject var viewModel = ViewModel()
	var body: some View {
		Text(viewModel.text)
	}
}

extension ContentView {
	class ViewModel: ObservableObject {
		@Published var text: String
	} 
}
```

And this method is excellent since it enables you to split this code into various files, preventing the need for large files with a huge amount of code.

However, there are numerous occasions where you want to access the properties of other ViewModels that are external to your view, like in the example that follows:

```swift
extension PreferencesView {
	class ViewModel: ObservableObject {
		[...]
	    @AppStorage("showFollowersInProfileView")
	    var showFollowersInProfileView: Bool = true
	}
}
```

```swift
extension ProfileView {
	class ViewModel: ObservableObject {
		[...]
		@Published var followers: Int
	}				   
}
```

```swift
struct ProfileView: View {
	@ObservedObject var viewModel = ViewModel()
	var body: some View {
		[...]
		Text("\(viewModel.followers)")
	}
}
```

How would you know if the setting for showing followers was enabled?

## Solution #1: Singletons
A very simple solution for this would be to add a singleton to `PreferencesView.viewModel` so that every view is able to access its shared state:
```swift
extension PreferencesView {
	class ViewModel: ObservableObject {
		[...]
		
		/// A singleton everybody can access to.
		static let shared = ViewModel()
 
	    @AppStorage("showFollowersInProfileView")
	    var showFollowersInProfileView: Bool = true
	}
}
```

Now, we would be able to access the preferences in our `ProfileView.swift` as follows:

```swift
struct ProfileView: View {
	@ObservedObject var viewModel = ViewModel()

	@ObservedObject
	var preferences = PreferencesView.ViewModel.shared

	var body: some View {
		[...]
		if preferences.showFollowersInProfileView {
			Text("\(viewModel.followers)")
		}
	}
}
```

This way, everytime the preferences view model changes, the changes will be reflected on other views.

> **NOTE**: If you use this approach, in order for changes to be reflected in other views, you need to modify `PreferencesView.ViewModel.shared`, not `PreferencesView.viewModel()`. This also applies to `PreferencesView`.

However, this method should only be used if the ViewModel in question is going to be accessed by several views. If you only wish to access a specific property in a few views, you should try the next method.

## Solution #2: Dependency Injection
From [Wikipedia](https://en.wikipedia.org/wiki/Dependency_injection):
> In software engineering, dependency injection is **a design pattern in which an object or function receives other objects or functions that it depends on**. A form of inversion of control, dependency injection aims to separate the concerns of constructing objects and using them, leading to loosely coupled programs.

Simply put, dependency injection means that rather than creating things for itself, any given piece of code you write should be given everything it needs to function by a parent. For instance, all the components an object or method need can be sent as arguments.

Let's say you have a VStack with two views, and you want both views to have access to the same view model.

```swift
struct ContentView: View {

    var body: some View {
        TabView {
            FirstView()
                .tabItem {
                    Label("First", systemImage: "house")
                }
            
            SecondView()
                .tabItem {
                    Label(
	                    "Second",
	                    systemImage: "square.and.pencil"
                    )
                }
        }
    }
}
```

```swift
struct FirstView: View {
    @StateObject var viewModel = ViewModel()
    var body: some View {
        VStack {
            Text("\(viewModel.count)")
            Button("Increment", action: {
                viewModel.incrementCount()
            })
            Button("Decrement", action: {
                viewModel.decrementCount()
            })
        }
    }
}

extension FirstView {
    class ViewModel: ObservableObject {
        @Published var count: Int = 0
        
        func incrementCount() {
            count+=1
        }
        
        func decrementCount() {
            count-=1
        }
    }
}
```

```swift
struct SecondView: View {
    var body: some View {
        Text("Hello, world!")
            .padding()
    }
}
```

How would you access the count in `SecondView`? Pretty simple: you would need `ContentView` to instantiate the view model, and then pass it to the ancestors through the constructor.

This can be achieved as follows:
```swift
struct ContentView: View {
    
    // We initialize the view model in the parent view
    @StateObject var viewModel = FirstView.ViewModel()

    var body: some View {
        TabView {
            // And we pass it down the ancestors through the constructor
            FirstView(viewModel: viewModel)
                .tabItem {
                    Label("First", systemImage: "house")
                }
            
            SecondView(firstViewModel: viewModel)
                .tabItem {
                    Label("Second", systemImage: "square.and.pencil")
                }
        }
    }
}
```

```swift
struct FirstView: View {
    @StateObject var viewModel: ViewModel

    var body: some View {
        VStack {
            Text("\(viewModel.count)")
            Button("Increment", action: {
                viewModel.incrementCount()
            })
            Button("Decrement", action: {
                viewModel.decrementCount()
            })
        }
    }
}

extension FirstView {
    class ViewModel: ObservableObject {
        @Published var count: Int = 0
        
        func incrementCount() {
            count+=1
        }
        
        func decrementCount() {
            count-=1
        }
    }
}
```

```swift
struct SecondView: View {
    @StateObject var firstViewModel: FirstView.ViewModel

    var body: some View {
        Text("Hello, world!")
            .padding()
    }
}
```

Notice how we first create the view model in the parent view, and then pass it down the ancestors through the constructors? This way, both `FirstView` and `SecondView` will share state.

Obviously, this example is overkill as we could have fixed this using simple `@Binding` and `@State` variables. However, when things get complex, we can use this approach.

### Using environment objects
While the example shown previously works correctly, SwiftUI provides us with the `@EnvironmentObject` property wrapper for data that needs to be shared among numerous views in your project. This enables us to distribute model data wherever it is required and guarantees that our views are continuously updated as soon as that data changes.

Consider `@EnvironmentObject` as a better, more straightforward alternative to using `@ObservedObject` on several views. You can create some data in some view and place it in the environment so that the rest of the views automatically have access to it.

The example shown previously can be rewritten as follows:

```swift
struct ContentView: View {
    
    // We initialize the view model in the parent view
    @StateObject var viewModel = FirstView.ViewModel()

    var body: some View {
        TabView {
            // And we pass it down the ancestors through the constructor
            FirstView()
                .environmentObject(viewModel)
                .tabItem {
                    Label("First", systemImage: "house")
                }
            
            SecondView()
                .environmentObject(viewModel)
                .tabItem {
                    Label("Second", systemImage: "square.and.pencil")
                }
        }
    }
}
```

```swift
struct FirstView: View {
    @EnvironmentObject var viewModel: ViewModel

    var body: some View {
        VStack {
            Text("\(viewModel.count)")
            Button("Increment", action: {
                viewModel.incrementCount()
            })
            Button("Decrement", action: {
                viewModel.decrementCount()
            })
        }
    }
}

extension FirstView {
    class ViewModel: ObservableObject {
        @Published var count: Int = 0
        
        func incrementCount() {
            count+=1
        }
        
        func decrementCount() {
            count-=1
        }
    }
}
```

```swift
struct SecondView: View {
    @EnvironmentObject var firstViewModel: FirstView.ViewModel

    var body: some View {
        Text("\(firstViewModel.count)")
            .padding()
    }
}
```

If you run the app, you will notice that it behaves exactly the same as in the previous example.

## Solution #3: Stores (Nested observable objects)
The final solution is to implement a `Store` into our app. Borrowed from Redux, a store is an immutable object tree that is in charge of maintaining an application's state.

While stores in Redux are a bit different, we can implement a similar behaviour in SwiftUI using nested observable objects.

We begin by creating our `Store` object as shown below, where we have `FirstView`'s view model as a published property:
```swift
final class Store: ObservableObject {
    @Published var firstViewModel = FirstView.ViewModel()
}
```

And we initialize it in our App  and pass it down the ancestor views through `.environmentObject`:
```swift
import SwiftUI

@main
struct ExampleApp: App {
    @StateObject var store = Store()

    var body: some Scene {
        WindowGroup {
            ContentView()
	            .environmentObject(store)
        }
    }
}
```

Alternatively, we can initialize it in our `ContentView`, though I prefer doing it in our app because that way we ensure that there is only one single Store.

Then, this should be our `ContentView`:
```swift
struct ContentView: View {

    var body: some View {
        TabView {
            FirstView()
                .tabItem {
                    Label("First", systemImage: "house")
                }
            
            SecondView()
                .tabItem {
                    Label(
	                    "Second",
	                    systemImage: "square.and.pencil"
                    )
                }
        }
    }
}
```

Notice how we no longer need to add `.environmentObject` to each of the views? Since we are initializing the object in our main app, every single ancestor view will have access to it, which is one of the main reasons why I prefer environment objects over plain dependency injection.

```swift
struct FirstView: View {
    @EnvironmentObject var store: Store

    var body: some View {
        VStack {
            Text("\(store.firstViewModel.count)")
            Button("Increment", action: {
                store.firstViewModel.incrementCount()
            })
            Button("Decrement", action: {
                store.firstViewModel.decrementCount()
            })
        }
    }
}

extension FirstView {
    class ViewModel: ObservableObject {
        @Published var count: Int = 0
        
        func incrementCount() {
            count+=1
        }
        
        func decrementCount() {
            count-=1
        }
    }
}
```

```swift
struct SecondView: View {
    @EnvironmentObject var store: Store

    var body: some View {
        Text("\(store.firstViewModel.count)")
            .padding()
    }
}
```

Now we are able to access the data from other views. When you launch the app, though, you'll see that it behaves differently than you might have anticipated and that UI changes are not reflected.

The reason for this is that SwiftUI only redraws the user interface when it detects a change in one of its state variables and state/observed objects. How does SwiftUI detect when one of the @Published variables change inside an observable object? Pretty simple: thanks to `objectWillChange`, which is, according to Apple, a publisher that emits before the object has changed.

You probably already know what a Publisher is if you've ever used Apple's `Combine` framework. If not, a publisher is, to put it briefly, something that discloses values that are subject to change and on which a subscriber subscribes to receive all those updates.

This publisher tells SwiftUI when an observed/state object has changed, and, this way, it is able to know when to redraw the UI. When you change a `@Published` variable, it is calling this `objectWillChange` publisher under the hood.

However, when a observable object changes inside another observable object, the parent object's `objectWillChange` is not triggered, instead, it is only triggered in the child, and, as a result, SwiftUI does not know when to update our interface.

Fortunately, there is an easy fix for this: all we need to do is subscribe for this publisher inside our store, and manually trigger the store's `objectWillChange` publisher.

This can be achieved using Combine as follows:

```swift
import Combine

final class Store: ObservableObject {
    @Published var firstViewModel = FirstView.ViewModel()
    
    private var anyCancellable: AnyCancellable? = nil

    init() {
        anyCancellable = firstViewModel.objectWillChange.sink { [weak self] _ in
            self?.objectWillChange.send()
        }
    }
}
```

Now, if you run the app, you will see that it works properly again.

## Conclusion
In this article you have seen several ways to share state between views in SwiftUI.

I hope these tricks help you have a cleaner and more organized codebase. Thanks for reading!