Set not Removing Duplicate Dates

174 Views Asked by At

I have a structure that displays entries sorted by date. The date is displayed once for all entries of the same date. The problem I have is that Set is not removing duplicate dates. If I have two entries with the same date, I have two blocks in the view with same entries in each block. See my original post here. If I enter multiple entries with the same date, uniqueDates (looking with the debugger) shows the same number of elements with the same date.

My theory is that Array(Set(wdvm.wdArray)) is sorting on the complete unformatted date which includes the time or other variables in each element. Therefore it thinks all the dates are unique. Is there anyway to use formatted dates for sorting?

struct WithdrawalView: View {

    @StateObject var wdvm = Withdrawal()

    var uniqueDates: [String] {

        Array(Set(wdvm.wdArray))                // This will remove duplicates, but WdModel needs to be Hashable
           .sorted { $0.wdDate < $1.wdDate }   // Compare dates
           .compactMap {
                $0.wdDate.formatted(date: .abbreviated, time: .omitted)    // Return an array of formatted the dates
            }
    }


    // filters entries for the given date
    func bankEntries(for date: String) -> [WdModel] {
        return wdvm.wdArray.filter { $0.wdDate.formatted(date: .abbreviated, time: .omitted) == date }
    }

    var body: some View {

        GeometryReader { g in


            VStack (alignment: .leading) {
                
                WDTitleView(g: g)
                List {
                    if wdvm.wdArray.isEmpty {

                        NoItemsView()

                    } else {

                        // outer ForEach with unique dates
                        ForEach(uniqueDates, id: \.self) { dateItem in  // change this to sort by date
                            Section {
                                // inner ForEach with items of this date
                                ForEach(bankEntries(for: dateItem)) { item in

                                    wdRow(g: g, item: item)

                                }
                            } header: {
                                Text("\(dateItem)")
                            }
                        }.onDelete(perform: deleteItem)
                    }
                }
                .navigationBarTitle("Bank Withdrawals", displayMode: .inline)
     

Below is the class used by this module

struct WdModel: Codable, Identifiable, Hashable {

    var id = UUID()
    var wdDate: Date        // bank withdrawal date
    var wdCode: String      // bank withdrawal currency country 3-digit code
    var wdBank: String      // bank withdrawal bank
    var wdAmtL: Double      // bank withdrawal amount in local currency
    var wdAmtH: Double      // bank withdrawal amount in home currency
    var wdCity: String
    var wdState: String
    var wdCountry: String
}


class Withdrawal: ObservableObject {

    @AppStorage(StorageKeys.wdTotal.rawValue) var withdrawalTotal: Double = 0.0

    @Published var wdArray: [WdModel]
       
    init() {

        if let wdArray = UserDefaults.standard.data(forKey: StorageKeys.wdBank.rawValue) {   
            if let decoded = try? JSONDecoder().decode([WdModel].self, from: wdArray) {
                self.wdArray = decoded
                return
            }
        }

        self.wdArray = []


    // save new withdrawal data    
func addNewWithdrawal(wdDate: Date, wdCode: String, wdBank: String, wdAmtL: Double, wdAmtH: Double, wdCity: String, wdState: String, wdCountry: String) -> () {

        self.withdrawalTotal +=  wdAmtH

        let item = WdModel(wdDate: wdDate, wdCode: wdCode, wdBank: wdBank, wdAmtL: wdAmtL, wdAmtH: wdAmtH, wdCity: wdCity, wdState: wdState, wdCountry: wdCountry)
        
        wdArray.append(item)

        if let encoded = try? JSONEncoder().encode(wdArray) { // save withdrawal entries
            UserDefaults.standard.set(encoded, forKey: StorageKeys.wdBank.rawValue)
        }
    }
}

one date. I am trying to display all entries of the same date under the one date. This example shows what I want but not the 3 copies of the date and entries.

1

There are 1 best solutions below

11
On BEST ANSWER

For Set to remove duplicate dates, try something like this:

var uniqueDates: [String] {
    Array(Set(wdvm.wdArray.map { $0.wdDate }))
        .sorted { $0 < $1 }
        .compactMap {
            $0.formatted(date: .abbreviated, time: .omitted)
        }
}

EDIT-3:

to display unique bankEntries for a given date, based on day, month and year of a date (not seconds,etc...):

struct ContentView: View {
    @State var wdArray = [WdModel]()
    
    let frmt: DateFormatter = {
        let formatter = DateFormatter()
        formatter.dateFormat = "MMM dd, yyyy"
        return formatter
    }()
    
    func bankEntries(for date: String) -> [WdModel] {
        return wdArray.filter { frmt.string(from: $0.wdDate) == date }
    }

    var uniqueDates: [String] {
        Array(Set(wdArray.map { frmt.string(from: $0.wdDate) }))
            .sorted { frmt.date(from: $0) ?? Date() < frmt.date(from: $1) ?? Date() }
            .compactMap { $0 }
    }

    var body: some View {
        List {
            // outer ForEach with unique dates
            ForEach(uniqueDates, id: \.self) { dateItem in  // change this to sort by date
                Section {
                    // inner ForEach with items of this date
                    ForEach(bankEntries(for: dateItem)) { item in
                      //  wdRow(g: g, item: item)
                        HStack {
                            Text(item.wdDate.formatted(date: .abbreviated, time: .omitted))
                            Text(item.wdCode).foregroundColor(.red)
                        }
                    }
                } header: {
                    Text("\(dateItem)")
                }
            }
        }
        .onAppear {
            let today = Date()  // <-- here
            let otherDate = Date(timeIntervalSince1970: 345678)
            
            wdArray = [
                WdModel(wdDate: today, wdCode: "EUR", wdBank: "Bank of Innsbruck", wdAmtL: 4575, wdAmtH: 1625, wdCity: "Innsbruck", wdState: " Tyrol", wdCountry: "Aus"),
                
                WdModel(wdDate: otherDate, wdCode: "CHF", wdBank: "Bank of Interlaken", wdAmtL: 6590, wdAmtH: 2305, wdCity: "Interlaken", wdState: "Bernese Oberland ", wdCountry: "CHF"),
                
                WdModel(wdDate: today, wdCode: "USD", wdBank: "Bank X", wdAmtL: 1200, wdAmtH: 3275, wdCity: "Las Vegas", wdState: "NV", wdCountry: "USA")
            ]
        }
    }
}