Skip to main content

In SwiftUI, has made creating a list of item very easy.  If you have an array containing the names of customers, these names can be displayed through the following code:

struct ContentView: View {

    let customers = ["Sarah","Vinodh","Dimitry","Lata"]

    var body: some View {
        List(customers){customer in
            Text(customer)
        }
    }
}

As you will have noticed, this is a static list.  Meaning that any changes to the customers array will not cause the List to refresh and be updated to the latest values.  And if like me, you rarely have a static list in your app.  Through using the @State variable  the List refresh dynamically post a change to the customers array.  Using the @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.  Sounds good, how do you achieve this step:

struct ContentView: View {

    @State var customers = ["Sarah","Vinodh","Dimitry","Lata"]

    var body: some View {
        List(customers){customer in
            Text(customer)
        }
    }
}

You can add to the List through using a server call to fetch customers information.  Once the information has been pulled form the server it will be automatically rendered in the List.  This can be achieved easily through the following code:

struct ContentView: View {

    @State var customers = ["Sarah","Vinodh","Dimitry","Lata"]

    var body: some View {
        VStack{
            Button(action: {
                self.loadFromServer()
            }){
                Text("Fetch customer list from your server")
            }
            List(customers, id: \.self){customer in
                Text(customer)
            }
        }
    }

    private func loadFromServer(){
        //do some network call and assign the result to self.customers
    }
}

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

Note, using the server network call in the View Struct violates the Single Responsibility Principle.  The purpose of the View is for rendering the UI elements onto the screen.  Therefore, we need to move the code related to networking out into a separate class. But as you might have already anticipated, moving networking code out of the View Struct also means that now we are unable to directly access the @State customers variable to update the list.  This issue is resolved through the use of ObservableObject.

We can start by creating a struct called Customer 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.

struct Customer: Identifiable {
    let id = UUID()
    let name:String
}

Now we can then create a class called CustomersObject and comform it to ObservableObject.

class CustomersObject : ObservableObject{
    @Published var customers = [Customer]()
}

Through making the customers variable as @Published, it means that whenever there are any changes to the customers 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:

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

struct ContentView: View {

    @ObservedObject var customersObject = CustomersObject()

    var body: some View {
        VStack{
            List(customersObject.customers, id: \.id){customer in
                Text(customer.name)
            }
        }
    }
}

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

class CustomersObject: ObservableObject {

    @Published var customers = [Customer]()

    func fetchFromServer(){
        //assign the result back to self.customers
    }
}

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

struct ContentView: View {

    @ObservedObject var customersObject = CustomersObject()

    var body: some View {
        VStack{
            Button(action: {
                self.customersObject.fetchFromServer()
            }){
                Text("Fetch the customers from server")
            }
            List(customersObject.customers, id: \.id){customer in
                Text(customer.name)
            }
        }
    }
}