Search code examples
swiftuiios15

Display Entry Date with Change in Orientation


I am struggling to get a function to display the date only if it is the first date in a list or an entry with a different date from the previous entry date. The code appears to work correctly except when changing the phone orientation. I have a variable (listDate) that holds the previous entry date and it is initialized to "" before dropping into this module. Changing the orientation is different. I have tried used orientation data with .onAppear and .onChange without any luck in getting the module to display the first entry with the date in the list upon change in orientation.

The function of interest (ckDateStatus) is at the bottom of this listing.

Thanks for you help.

struct WithdrawalView: View {

    @EnvironmentObject var bank: BankWithdrawal

    var body: some View {

        GeometryReader { g in
            VStack (alignment: .leading) {
                List {

                   WDTitleView(g: g)

                   ForEach(bank.wdItem, id: \.id) { item in
                         showSingleWdRow(g: g, item: item )

                   }.onDelete(perform: deleteItem)
                }
            }
        }
    }
}

struct showSingleWdRow: View {

    @EnvironmentObject var bank: BankWithdrawal
    @EnvironmentObject var orientInfo: DeviceOrientation

    var g: GeometryProxy
    var item: WdItem

    var body: some View {

        let entryDate = self.ckDateStatus( cDate: item.wdDate)

        // if withdrawal date is the same as the previous entry skip date and proceed to time
        if !entryDate.isEmpty {
            Text(entryDate)
        }

        // show time, local and home currency amount
        ShowRowTopLine(g:g, item: item)
            .font(.subheadline)

        // show withdrawal location
        if g.size.width > g.size.height {
            Text("\(item.wdCity), \(item.wdState), \(item.wdCountry)")
                
            //}

            //       .onChange(of: orientInfo.orientation == .portrait) { newValue in
            //                bank.listDate = ""
            //        }

            //            if orientInfo.orientation == .portrait {
            //            Text("portrait")
            //            } else {
            //                Text("landscape")
            //            }

        }
    }

    // check if the entry date is the 1st entry in list or the same as previous date
    func ckDateStatus( cDate: Date) -> (String) {
        var outDate: String = ""

        // initialzie the entry date
        let entryDate = cDate.formatted(.dateTime.year().day().month(.wide))

        // if saved date is blank -> no previous entries
        if bank.listDate.isEmpty {
            outDate = entryDate
        }
        else {   // saved date is not blank
            if bank.listDate == entryDate {
                outDate = ""
            }
            else { // date entries different
                outDate = entryDate
            }
        }
        bank.listDate = entryDate

        // outDate returns blank or the current date
        return (outDate)
    }
}

Solution

  • The issue with your approach is that SwiftUI only redraws cells that have to be redrawn. So on change of orientation it is not guaranteed that all cells will be refreshed.

    You could solve that by adding to your outer! view:

    .id(orientInfo)    
    

    This would force the view to redraw when orientInfo changes.

    But I would suggest another way by pre-organizing your data.
    You can create an array of all the unique dates you have, then filter entries by that date. These arrays can then be used to display your data the way you want – and it also will safely work on orientation change.

    struct ContentView: View {
        
        // dummy data
        let bank: [BankWithdrawal] = [
            BankWithdrawal(wdDate: "22/03/01", wdAmount: 150, wdCity: "New York", wdState: "NY"),
            BankWithdrawal(wdDate: "22/03/01", wdAmount: 100, wdCity: "Miami", wdState: "FL"),
            BankWithdrawal(wdDate: "22/03/02", wdAmount: 300, wdCity: "Orlando", wdState: "FL"),
            BankWithdrawal(wdDate: "22/03/02", wdAmount: 150, wdCity: "S.F.", wdState: "CA"),
            BankWithdrawal(wdDate: "22/03/02", wdAmount: 200, wdCity: "Miami", wdState: "FL"),
            BankWithdrawal(wdDate: "22/03/03", wdAmount: 150, wdCity: "New York", wdState: "NY"),
            BankWithdrawal(wdDate: "22/03/04", wdAmount: 500, wdCity: "S.F.", wdState: "CA"),
            BankWithdrawal(wdDate: "22/03/04", wdAmount: 150, wdCity: "Miami", wdState: "FL"),
            BankWithdrawal(wdDate: "22/03/04", wdAmount: 250, wdCity: "New York", wdState: "NY"),
            BankWithdrawal(wdDate: "22/03/05", wdAmount: 150, wdCity: "S.F.", wdState: "CA")
        ]
        
        // creates a list of unique date entries
        // (goes through all elements, saves them in a set, checks if already in set)
        var uniqueBankDates: [BankWithdrawal] {
            var seen: Set<String> = []
            return bank.filter { seen.insert($0.wdDate).inserted }
        }
        
        // filters entries for the given date
        func bankEntries(for date: String) -> [BankWithdrawal] {
            return bank.filter { $0.wdDate == date }
        }
        
        var body: some View {
            List {
                // outer ForEach with unique dates
                ForEach(uniqueBankDates) { dateItem in
                    Section {
                        // inner ForEach with items of this date
                        ForEach(bankEntries(for: dateItem.wdDate)) { item in
                            Text("$\(item.wdAmount, specifier: "%.2f")  \(item.wdCity), \(item.wdState)")
                        }
                    } header: {
                        Text(dateItem.wdDate)
                    }
                }
            }
        }
    }
    
    struct BankWithdrawal: Identifiable {
        let id = UUID()
        var wdDate: String
        var wdAmount: Double
        var wdCity: String
        var wdState: String
        var wdCountry: String = "USA"
    }
    

    enter image description here

    EDIT: When using Date()you can do it like this:

    struct ContentView: View {
        
        // dummy data
        let bank: [BankWithdrawal] = [
            BankWithdrawal(wdDate: Date(), wdAmount: 150, wdCity: "New York", wdState: "NY"),
            BankWithdrawal(wdDate: Date(), wdAmount: 100, wdCity: "Miami", wdState: "FL"),
            BankWithdrawal(wdDate: Date(), wdAmount: 300, wdCity: "Orlando", wdState: "FL"),
            BankWithdrawal(wdDate: Date(), wdAmount: 150, wdCity: "S.F.", wdState: "CA"),
            BankWithdrawal(wdDate: Date(), wdAmount: 200, wdCity: "Miami", wdState: "FL"),
            BankWithdrawal(wdDate: Date(), wdAmount: 150, wdCity: "New York", wdState: "NY"),
            BankWithdrawal(wdDate: Date() + 60*60*24*1, wdAmount: 500, wdCity: "S.F.", wdState: "CA"),
            BankWithdrawal(wdDate: Date() + 60*60*24*1, wdAmount: 150, wdCity: "Miami", wdState: "FL"),
            BankWithdrawal(wdDate: Date() + 60*60*24*1, wdAmount: 250, wdCity: "New York", wdState: "NY"),
            BankWithdrawal(wdDate: Date() + 60*60*24*1, wdAmount: 150, wdCity: "S.F.", wdState: "CA")
        ]
        
        // creates a list of unique date entries
        // (goes through all elements, saves them in a set, checks if already in set)
        var uniqueBankDates: [String] {
            var seen: Set<String> = []
            return bank.filter { seen.insert($0.wdDate.formatted(date: .abbreviated, time: .omitted)).inserted }
            .map {$0.wdDate.formatted(date: .abbreviated, time: .omitted) }
        }
        
        // filters entries for the given date
        func bankEntries(for date: String) -> [BankWithdrawal] {
            return bank.filter { $0.wdDate.formatted(date: .abbreviated, time: .omitted) == date }
        }
        
        var body: some View {
            List {
                // outer ForEach with unique dates
                ForEach(uniqueBankDates, id: \.self) { dateItem in
                    Section {
                        // inner ForEach with items of this date
                        ForEach(bankEntries(for: dateItem)) { item in
                            Text("$\(item.wdAmount, specifier: "%.2f")  \(item.wdCity), \(item.wdState)")
                        }
                    } header: {
                        Text("\(dateItem)")
                    }
                }
            }
        }
    }
    
    struct BankWithdrawal: Identifiable {
        let id = UUID()
        var wdDate: Date
        var wdAmount: Double
        var wdCity: String
        var wdState: String
        var wdCountry: String = "USA"
    }