简体   繁体   English

SwiftUI - 将结构化数组排序并显示日期项到一个部分 Header

[英]SwiftUI - Sorting a structured array and show date item into a Section Header

I'm trying to show some section Header which is based on data in my structured array.我试图展示一些 Header 部分,它基于我的结构化数组中的数据。

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所以在我的数组中,我有一个输入“日期”格式,如: 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 :NewFlightConfig

struct NewFlightConfig{

    var date: String = ""

    var depPlace: String = ""
    var arrPlace: String = ""

}

The TextField where I ask for the date:我要求日期的TextField

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...我试图处理Dictionary以填充 SectionHeader 文本但似乎没有用的地方......

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.我不太熟悉如何将数据排序和分组数据放入 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谢谢你的帮助

You could try this approach, where a function func asDate(...) is used to transform your String date to a Date on the fly.您可以尝试这种方法,其中使用 function func asDate(...)将您的String日期转换为运行中的Date Then using Set and map , to get unique dates for the sections.然后使用Setmap来获取这些部分的唯一日期。 These unique dates are sorted using the func asDate(...) .这些唯一日期使用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 .另一种方法是将Flight中的String日期更改为Date类型。 Then using Set , map and sorted , to get unique and sorted dates for the sections.然后使用Setmapsorted ,获得各部分的唯一和排序日期。

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. EDIT-1:这是另一种使用class FlightModel: ObservableObject来保存数据并在flights更改时更新 UI 的方法。 It also has a convenience computed property for the uniqueDates .它还为uniqueDates提供了一个方便的计算属性。 So in your addView , pass the flightModel to it (eg @EnvironmentObject) and add new Flight to the flightModel .因此,在您的addView中,将flightModel传递给它(例如@EnvironmentObject)并将新的Flight添加到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)
        }
    }
}

In response to @workingdog support Ukraine in the comments just before.作为对@workingdog 在之前评论中支持乌克兰的回应。

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.当我通过应用程序中的页面将值 append append 输入 testFlight 时,用户可以在其中填写一些 textFields 以设置日期、dePlace 和 arrPlace,然后关闭我的页面,滚动视图未正确更新。 I've my new item but I have no Section Header...我有我的新项目,但我没有 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 !要更新滚动视图,我 go 在另一个页面中并返回(重新加载.onAppear )到我有滚动视图的页面,现在它适用于所有部分 header 但是!

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...如果只有一个日期部分 header 是正确的,但如果有两个或多个具有相同日期的项目,它会为每个项目创建一个新的部分 header 但它会在每个部分中添加所有具有相同日期的项目...

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM