SwiftUI: How to use List with ObservableObject
Tags: swift, swiftui, observableobject, ios
Published: 2020-06-20 16:41:09

---
date: 2020-04-30 18:30
description:  ObservableObject is very useful in SwiftUI List to display dynamic content
tags: swift, swiftui, observableobject, ios
---

In SwiftUI, it is very easy to construct a list of item. For example, let say we have an array containing the names of our users, we can display these names in a list by the following code: 

```swift
struct ContentView: View {
    
    let users = ["John","Peter","Jane"]
    
    var body: some View {
        List(users){user in
            Text(user)
        }
    }
}
```
In this case, we can make a list which consists of the names of all the users. However, this list of names is static, meaning that any changes to the users array will not cause the List to refresh and be updated to the latest values. To make the List to refresh whenever there is changes to the users array, we can make use of the `@State` variable. With the use of `@State` in front of a variable, SwiftUI will listen for any changes to that variable, and automatically render the `View` again with the latest values. 

```swift
struct ContentView: View {
    
    @State var users = ["John","Peter","Jane"]
    
    var body: some View {
        List(users){user in
            Text(user)
        }
    }
}
```
To see how this might be useful, let's say that we have a button that makes a network call to fetch the users information from our server, we can easily render the List with the network call result easily with the following code:

```swift
struct ContentView: View {
    
    @State var users = ["John","Peter","Jane"]
    
    var body: some View {
        VStack{
            Button(action: {
                self.loadFromServer()
            }){
                Text("Fetch From Server")
            }
            List(users, id: \.self){user in
                Text(user)
            }
        }
    }
    
    private func loadFromServer(){
        //do some network call and assign the result to self.users
    }
}

```
With the code above, when the result from the network call is ready and then assigned to self.users, SwiftUI detects changes to the variable and will redraw the `List` with the updated values. 

<br/>

While the code above works, it is not a good idea to put our networking code in the `View` Struct. `View` is only responsible for rendering the UI elements onto the screen. Having networking code inside a `View` Struct violates the Single Responsibility Principle. Hence, we should move the code related to networking out into separate class. However, moving networking code out of the `View` Struct also means that now we are unable to directly access the `@State` users variable to update the list. The solution : `ObservableObject`. 

<br/>

We can start by creating a struct called `User` with two properties: `name` and `id`. The reason for the need for id is to allow `List` in SwiftUI to be able to unique identify and keep track of the changes to the data source. 

```swift
struct User: Identifiable {
    let id = UUID()
    let name:String
}

```
Next, we can then create a class called UserContainer, and comform it to `ObservableObject`.

```swift
class UsersContainer : ObservableObject{
    @Published var users = [User]()
}
```
By marking the users variable as `@Published`, it means that whenever there is any changes to the users variable, the instances of the class that are "subscribed" to it will be notified, prompting SwiftUI to re-render the `View`. The final step is to connect the data source to our `View`:

<br/>

To do this, we will create an instance of the UsersContainer Class in the ContentView. In order to "subscribe" to be notified of any changes to the data, we have to mark it with `@ObservedObject`: 

```swift
struct ContentView: View {
    
    @ObservedObject var usersContainer = UsersContainer()
    
    var body: some View {
        VStack{
            List(usersContainer.users, id: \.id){user in
                Text(user.name)
            }
        }
    }
}

```
Now with the current arrangement, supposed that we want to make a network call to the server to retrieve the list of users, instead of doing it in the ContentView, we can now do the networking logic in the UsersContainer Class

```swift
class UsersContainer : ObservableObject{
   
    @Published var users = [User]()
    
    func fetchFromServer(){
        //assign the result back to self.users
    }
}

```
We can now add a button in our `ContentView` to trigger the fetchFromServer() call like this: 

```swift
struct ContentView: View {
    
    @ObservedObject var usersContainer = UsersContainer()
    
    var body: some View {
        VStack{
            Button(action: {
                self.usersContainer.fetchFromServer()
            }){
                Text("Fetch From Server")
            }
            List(usersContainer.users, id: \.id){user in
                Text(user.name)
            }
        }
    }
}

```