Viktor Goleš
Viktor Goleš

Reputation: 33

How to sort a list with structs by date in SwiftUI?

I have a struct like this:

import Foundation

struct Product: Identifiable {
    var name: String
    let expirationDate: Date
    let id = UUID()
}

and a list like this:

import SwiftUI

struct ContentView: View {
    
    @Binding var products: [Product]
    
    var dateFormatter: DateFormatter {
           let formatter = DateFormatter()
           formatter.dateStyle = .medium
           return formatter
       }
    
    var body: some View {
        
        VStack {
           List {
              ForEach(products) { product in
                 HStack {
                    Text(product.name)
                    Spacer()
                    Text(self.dateFormatter.string(from: product.expirationDate))
                 }
              }
           }
        }

how can I sort this list so that the closest expiration date from now is on top of the list?

I don't even know how to get just the expirationDates from products array. I would appreciate any help. Thank you in advance!

Upvotes: 1

Views: 1064

Answers (2)

malhal
malhal

Reputation: 30726

They recently added Table that gives you some sort functionality. The docs say to use onChange but I prefer to use a struct with didSet which prevents body being recomputed twice and also allows an initial sort. e.g.

import SwiftUI

struct Product: Identifiable {
    let id = UUID()
    let name: String
    let expirationDate: Date
}

var myProducts: [Product] = [Product(name: "Car", expirationDate: Date.now.advanced(by: 5400)), Product(name: "Boat", expirationDate: Date.now)]

struct ProductsData {
    var sortedProducts: [Product]
    var sortOrder = [KeyPathComparator(\Product.name)] {
        didSet {
            sortedProducts.sort(using: sortOrder)
        }
    }
    
    init() {
        sortedProducts = myProducts.sorted(using: sortOrder)
    }
}

struct ContentView: View {
    @State private var data = ProductsData()
    
    var body: some View {
        ProductsTable(data: $data)
    }
}

struct ProductsTable: View {
    @Environment(\.horizontalSizeClass) private var horizontalSizeClass
    @Binding var data: ProductsData
    
    var body: some View {
        Table(data.sortedProducts, sortOrder: $data.sortOrder) {
            TableColumn("Name", sortUsing: KeyPathComparator(\Product.name)) { product in
                if horizontalSizeClass == .compact {
                    LabeledContent {
                        Text(product.expirationDate, format: .dateTime)
                    } label: {
                        Text(product.name)
                    }
                } else {
                    Text(product.name)
                }
            }
            TableColumn("Expiration Date", sortUsing: KeyPathComparator(\Product.expirationDate)) { product in
                Text(product.expirationDate, format: .dateTime)
            }
        }
    }
}

iPad Mini Screenshot

Upvotes: 2

RelativeJoe
RelativeJoe

Reputation: 5104

You need to sort your array based on timeIntervalSinceNow:

$0.expirationDate.timeIntervalSinceNow < $1.expirationDate.timeIntervalSinceNow

Edit(Thanks to Martin R) You can compare Date directly:

$0.expirationDate < $1.expirationDate

So in your view, ForEach(products) becomes:

Foreach(products.sorted(by: {$0.expirationDate < $1.expirationDate}))

Upvotes: 3

Related Questions