I'm trying to show some section Header which is based on data in my structured array.
When I add a value in my array from my app I ask to enter a date. This one is save as a String cause I don't know how to save it differently from a Textfield..
So in my array I've got an enter "date" formatting like : DD/MM/YYYY
Now I need to have a view where I list all the array Items sorting by date, where the most recent date is show on the top of the screen and more the user scroll down more the date is far in a past.
So my structured array is defined like that :
struct Flight: Identifiable{
let id = UUID().uuidString
let date: String
let depPlace: String
let arrPlace: String
init (date: String, depPlace: String, arrPlace: String){
self.date = date
self.depPlace = depPlace
self.arrPlace = arrPlace
}
init(config: NewFlightConfig){
self.date = config.date
self.depPlace = config.depPlace
self.arrPlace = config.arrPlace
}
}
and NewFlightConfig
:
struct NewFlightConfig{
var date: String = ""
var depPlace: String = ""
var arrPlace: String = ""
}
The TextField
where I ask for the date :
TextField("DD/MM/YYYY", text: $flightConfig.date)
.padding()
.background(.white)
.cornerRadius(20.0)
.keyboardType(.decimalPad)
.onReceive(Just(flightConfig.date)) { inputValue in
if inputValue.count > 10 {
self.flightConfig.date.removeLast()
}else if inputValue.count == 2{
self.flightConfig.date.append("/")
}else if inputValue.count == 5{
self.flightConfig.date.append("/")
}
}
Finally my Homepage with my list which is defined as follow :
ScrollView{
VStack {
ForEach(flightLibrary.testFlight) {date in
Section(header: Text(date.date).font(.title2).fontWeight(.semibold)) {
ForEach(flightLibrary.testFlight) {flight in
ZStack {
RoundedRectangle(cornerRadius: 16, style: .continuous)
.fill(Color.white)
.shadow(color: Color(Color.RGBColorSpace.sRGB, white: 0, opacity: 0.2), radius: 4)
LogbookCellView(flight: flight)
}
}
}
}
}.padding(.horizontal, 16)
}
Where I've trying to deal with Dictionary
to fill the SectionHeader Text but seems to didn't work...
var entryCollatedByDate: [String : [Flight]] {
Dictionary(grouping: flightLibrary, by: { $0.date })
}
I'm not very familiar with how to sorted data and grouped data into arrays.
My final objectif is to have something like that :
Section Header 1 -> 15/09/2022
Array Items 1 -> last items with same Header date
Array Items 2 -> last items with same Header date
Section Header 2 -> 14/09/2022
Array Items 3 -> last items with same Header date
Array Items 4 -> last items with same Header date
Array Items 5 -> last items with same Header date
[...]
Section Header N -> DD/MM/YYYY
Array Items N -> last items with same Header date
Hope to be clear about my problem
Thanks for your help
CodePudding user response:
In response to @workingdog support Ukraine in the comments just before.
I have no errors in my code but with this array :
class FlightLibrary: ObservableObject{
@Published var testFlight = [
Flight(date: Date(), depPlace: "LFLI", arrPlace: "LFLP"),
Flight(date: Date(), depPlace: "LFLP", arrPlace: "LFLB"),
Flight(date: Date(), depPlace: "LFLB", arrPlace: "LFLG")
]
}
This return something like :
Section Header 1 : Today Date
Item 1 :testFlight[0]
Item 2 :testFlight[0]
Item 3 :testFlight[0]
Section Header 2 : Today Date
Item 1 :testFlight[0]
Item 2 :testFlight[0]
Item 3 :testFlight[0]
When I append a value into testFlight, via a page in the app where user could fill some textFields to set date, dePlace and arrPlace, then dismiss my page the scrollview is not updated correctly. I've my new item but I have no Section Header ...
My add button code in the other view :
@Environment(\.dismiss) private var dismiss
@ObservedObject var flightLibrary: FlightLibrary
@State var flightConfig = NewFlightConfig()
.navigationBarItems(trailing: Button(action: {
let newFlight = Flight(config: flightConfig)
flightLibrary.testFlight.append(newFlight)
dismiss()
}, label: {
Text("Save")
}))
To update the scrollview I go in an other page and back (to reload the .onAppear
) to the page where I have my scrollview and now it works with all section header but !
If there is only one date section header is correct but if there is two or more item with the same date it create a new section header for each item but it add all the item with the same date in each section ...
Exemple :
Item1 = Date 1
Item2 = Date 1
Item3 = Date 2
Item4 = Date 1
result of :
Section Header 1 : Date1
Item 1
Item 2
Item 4
Section Header 2 : Date1
Item 1
Item 2
Item 4
Section Header 3 : Date1
Item 1
Item 2
Item 4
Section Header 4 : Date2
Item 3
CodePudding user response:
You could try this approach, where a function func asDate(...)
is used to transform your String
date
to a Date
on the fly. Then using Set
and map
, to get unique dates for the sections.
These unique dates are sorted using the func asDate(...)
.
struct ContentView: View {
@State var flightLibrary = [Flight(date: "14/09/2022", depPlace: "depPlace-1", arrPlace: "arrPlace-1"),
Flight(date: "15/09/2022", depPlace: "depPlace-2", arrPlace: "arrPlace-2"),
Flight(date: "12/09/2022", depPlace: "depPlace-3", arrPlace: "arrPlace-3"),
Flight(date: "14/09/2022", depPlace: "depPlace-1.2", arrPlace: "arrPlace-1.2")]
func asDate(_ str: String) -> Date {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "dd/MM/yyyy"
return dateFormatter.date(from: str) ?? Date()
}
@State var uniqueDates = [String]()
var body: some View {
ScrollView{
VStack {
ForEach(uniqueDates, id: \.self) { date in
Section(header: Text(date).font(.title2).fontWeight(.semibold)) {
ForEach(flightLibrary.filter({$0.date == date})) { flight in
ZStack {
RoundedRectangle(cornerRadius: 16, style: .continuous)
.fill(Color.white)
.shadow(color: Color(Color.RGBColorSpace.sRGB, white: 0, opacity: 0.2), radius: 4)
Text(flight.arrPlace)
}
}
}
}
}.padding(.horizontal, 16)
}
.onAppear {
// unique and sorted dates
uniqueDates = Array(Set(flightLibrary.map{$0.date})).sorted(by: {asDate($0) > asDate($1)})
}
}
}
An alternative approach is to change the String
date in Flight
to type Date
.
Then using Set
, map
and sorted
, to get unique and sorted dates for the sections.
struct ContentView: View {
@State var flightLibrary = [Flight]()
@State var uniqueDates = [Date]()
let frmt = DateFormatter()
var body: some View {
ScrollView{
VStack {
ForEach(uniqueDates, id: \.self) { date in
Section(header: Text(frmt.string(from: date)).font(.title2).fontWeight(.semibold)) {
ForEach(flightLibrary.filter({Calendar.current.isDate($0.date, inSameDayAs: date)})) { flight in
ZStack {
RoundedRectangle(cornerRadius: 16, style: .continuous)
.fill(Color.white)
.shadow(color: Color(Color.RGBColorSpace.sRGB, white: 0, opacity: 0.2), radius: 4)
Text(flight.arrPlace)
}
}
}
}
}.padding(.horizontal, 16)
}
.onAppear {
frmt.dateFormat = "dd/MM/yyyy"
frmt.timeZone = TimeZone(identifier: "UTC")
// for testing only
flightLibrary = [Flight(date: frmt.date(from: "14/09/2022")!, depPlace: "depPlace-1", arrPlace: "arrPlace-1"),
Flight(date: frmt.date(from: "15/09/2022")!, depPlace: "depPlace-2", arrPlace: "arrPlace-2"),
Flight(date: frmt.date(from: "12/09/2022")!, depPlace: "depPlace-3", arrPlace: "arrPlace-3"),
Flight(date: frmt.date(from: "14/09/2022")!, depPlace: "depPlace-1.2", arrPlace: "arrPlace-1.2")]
// unique and sorted dates
uniqueDates = Array(Set(flightLibrary.map{$0.date})).sorted(by: {$0 > $1})
}
}
}
struct Flight: Identifiable, Hashable {
let id = UUID().uuidString
let date: Date // <-- here
let depPlace: String
let arrPlace: String
init (date: Date, depPlace: String, arrPlace: String){ // <-- here
self.date = date
self.depPlace = depPlace
self.arrPlace = arrPlace
}
init(config: NewFlightConfig) {
self.date = config.date
self.depPlace = config.depPlace
self.arrPlace = config.arrPlace
}
}
struct NewFlightConfig {
var date: Date = Date() // <-- here
var depPlace: String = ""
var arrPlace: String = ""
}
EDIT-1: here is another approach that uses a class FlightModel: ObservableObject
to hold your data and update the UI whenever flights
is changed. It also has a convenience computed property for theuniqueDates
.
So in your addView
, pass the flightModel
to it (e.g @EnvironmentObject) and add new Flight
to the flightModel
.
class FlightModel: ObservableObject {
@Published var flights = [Flight]()
var uniqueDates: [Date] {
let arr = flights.compactMap{frmt.date(from: frmt.string(from: $0.date))}
return Array(Set(arr.map{$0})).sorted(by: {$0 > $1})
}
let frmt = DateFormatter()
init() {
frmt.dateFormat = "dd/MM/yyyy"
frmt.timeZone = TimeZone(identifier: "UTC")
getData()
}
func getData() {
// for testing only
flights = [
Flight(date: Date(), depPlace: "LFLI", arrPlace: "LFLP"),
Flight(date: Date(), depPlace: "LFLP", arrPlace: "LFLB"),
Flight(date: Date(), depPlace: "LFLB", arrPlace: "LFLG"),
Flight(date: frmt.date(from: "14/09/2022")!, depPlace: "depPlace-1", arrPlace: "arrPlace-1"),
Flight(date: frmt.date(from: "15/09/2022")!, depPlace: "depPlace-2", arrPlace: "arrPlace-2"),
Flight(date: frmt.date(from: "12/09/2022")!, depPlace: "depPlace-3", arrPlace: "arrPlace-3"),
Flight(date: frmt.date(from: "14/09/2022")!, depPlace: "depPlace-1.2", arrPlace: "arrPlace-1.2")
]
}
}
struct ContentView: View {
@StateObject var flightModel = FlightModel()
var body: some View {
ScrollView {
VStack {
ForEach(flightModel.uniqueDates, id: \.self) { date in
Section(header: Text(flightModel.frmt.string(from: date)).font(.title2).fontWeight(.semibold)) {
ForEach(flightModel.flights.filter({Calendar.current.isDate($0.date, inSameDayAs: date)})) { flight in
ZStack {
RoundedRectangle(cornerRadius: 16, style: .continuous)
.fill(Color.white)
.shadow(color: Color(Color.RGBColorSpace.sRGB, white: 0, opacity: 0.2), radius: 4)
Text(flight.arrPlace)
}
}
}
}
}.padding(.horizontal, 16)
}
}
}