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)
}
}
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"
}
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"
}