Table with SwiftUI (Episode I°)

If a list is a must-have for the iPhone, similarly, the Table can be essential for the iPad. Typically, when developing applications for larger screens, such as iPads or computers, it’s advantageous to be familiar with this component.

This post is the first in a series about Table.

For this post, I’d like to start with the code from this post https://nicoladefilippo.com/async-image-in-swiftui/, so we don’t reinvent the wheel and can focus solely on the relevant part.

Just to cover the basics, we retrieve a list of beers from here with this code:

struct ContentView: View {
    @State var beers: [Beer] = []
    @State var page = 0
    
    var body: some View {
        List(beers) { beer in
            HStack {
                AsyncImage(url: URL(string: beer.image_url)) { image in
                    image.resizable().scaledToFit()
                } placeholder: {
                    ProgressView()
                }.frame(width: 50, height: 50)
                Text(beer.name)
            }
        }.onAppear {
            Task {
                await getBeers()
            }
        }
    }
    
    func getBeers() async {
        do {
            page += 1
            let url = URL(string: "https://api.punkapi.com/v2/beers?page=\(page)&per_page=30")!
            let (data, _) = try await URLSession.shared.data(from: url)
            let beersLoaded = try JSONDecoder().decode([Beer].self, from: data)
            beers = beersLoaded + beers
        } catch {
            print("Some error")
        }
    }
}

Where, for the beer, we retrieve only the name and the image:

struct Beer: Codable, Identifiable {
    var id: Int
    var name: String
    var image_url: String
}

This is the starting point. Now, suppose we have an iPad and we want to show more information:

struct Beer: Codable, Identifiable {
    var id: Int
    var name: String
    var first_brewed: String
    var image_url: String
    var abv: Double
}

To have this:

he first step is to replace the List with a TableView:

Table(beers) {
            TableColumn("Name", value: \.name)
            TableColumn("First Brew", value: \.first_brewed)
            TableColumn("ABV") { beer in
                Text("\(beer.abv)")
            }
            TableColumn("Image") { beer in
                AsyncImage(url: URL(string: beer.image_url)) { image in
                    image.resizable().scaledToFit()
                } placeholder: {
                    ProgressView()
                }
                .frame(width: 100, height: 100)
            }
        }

The table operates on the array of beers. After that, we add the column, where we assign the title name and the value (at least for the strings). For the ABV, which is a double, we pass the beer to the content (in this case, simple text), the same goes for the column that displays the image.

Now, we want to add a sorting option, to sort by name:

@State private var sortOrder = [KeyPathComparator(\Beer.name, order: .reverse)]

Change the Table definition in this way:

Table(beers, sortOrder: $sortOrder)

It’s not sufficient if we want the order to change by tapping on the column header; we have to add this:

.onChange(of: sortOrder) {
            beers.sort(using: sortOrder)

To have the beers list ordered right after it’s downloaded, we add this to the getBeers function:

beers.sort(using: sortOrder)

Merging everything together:

struct ContentView: View {
    @State var beers: [Beer] = []
    @State var page = 0
    @State private var sortOrder = [KeyPathComparator(\Beer.name, order: .reverse)]
    var body: some View {
        Table(beers, sortOrder: $sortOrder) {
            TableColumn("Name", value: \.name)
            TableColumn("First Brew", value: \.first_brewed)
            TableColumn("ABV") { beer in
                Text("\(beer.abv)")
            }
            TableColumn("Image") { beer in
                AsyncImage(url: URL(string: beer.image_url)) { image in
                    image.resizable().scaledToFit()
                } placeholder: {
                    ProgressView()
                }
                .frame(width: 100, height: 100)
                
            }
        }.onAppear {
            Task {
                await getBeers()
            }
        }.onChange(of: sortOrder) {
            beers.sort(using: sortOrder)
       }
    }
    func getBeers() async {
        do {
            page += 1
            let url = URL(string: "https://api.punkapi.com/v2/beers?page=\(page)&per_page=30")!
            let (data, _) = try await URLSession.shared.data(from: url)
            let beersLoaded = try JSONDecoder().decode([Beer].self, from: data)
            beers = beersLoaded + beers
            beers.sort(using: sortOrder)
        } catch {
            print("Some error")
        }
    }
}

Now we are able to create a sorted table.

Note: English is not my native language, so I apologize for any errors. I use AI solely to generate the banner of the post; the content is human-generated.

share this post with friends

Picture of Nicola De filippo

Nicola De filippo

I'm a software engineer who adds to the passion for technologies the wisdom and the experience without losing the wonder for the world. I love to create new projects and to help people and teams to improve

Leave a comment

Your email address will not be published. Required fields are marked *

Who I am

I'm a software engineer who adds to the passion for technologies the wisdom and the experience without losing the wonder for the world. I love to create new projects and to help people and teams to improve.

Follow Me Here

Get The Latest Updates

Periodically receive my super contents on coding and programming

join the family;)

Recent Posts