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.