如何在 SwiftUI List/ForEach 中动态创建部分并避免“无法推断复杂的闭包返回类型" [英] How to dynamically create sections in a SwiftUI List/ForEach and avoid "Unable to infer complex closure return type"
问题描述
我正在尝试重新创建与股票 iOS 日历应用程序中的事件列表相匹配的视图.我有以下代码生成一个事件列表,每个事件按日期分成自己的部分:
I'm trying to recreate a view that matches the event list in the stock iOS Calendar app. I have the following code which generates a list of events with each event separated into its own section by Date:
var body: some View {
NavigationView {
List {
ForEach(userData.occurrences) { occurrence in
Section(header: Text("(occurrence.start, formatter: Self.dateFormatter)")) {
NavigationLink(
destination: OccurrenceDetail(occurrence: occurrence)
.environmentObject(self.userData)
) {
OccurrenceRow(occurrence: occurrence)
}
}
}
}
.navigationBarTitle(Text("Events"))
}.onAppear(perform: populate)
}
这段代码的问题在于,如果同一日期有两个事件,它们会被分成具有相同标题的不同部分,而不是被组合到同一部分.
The problem with this code is that if there are two events on the same date, they are separated into different sections with the same title instead of being grouped together into the same section.
作为一个 Swift 新手,我的直觉是做这样的事情:
As a Swift novice, my instinct is to do something like this:
ForEach(userData.occurrences) { occurrence in
if occurrence.start != self.date {
Section(header: Text("(occurrence.start, formatter: Self.dateFormatter)")) {
NavigationLink(
destination: OccurrenceDetail(occurrence: occurrence)
.environmentObject(self.userData)
) {
OccurrenceRow(occurrence: occurrence)
}
}
} else {
NavigationLink(
destination: OccurrenceDetail(occurrence: occurrence)
.environmentObject(self.userData)
) {
OccurrenceRow(occurrence: occurrence)
}
}
self.date = occurrence.start
但在 Swift 中,这给了我错误无法推断复杂闭包返回类型;添加显式类型以消除歧义",因为我在 ForEach{} 中调用任意代码(self.date =occurrence.start),这是不允许.
But in Swift, this gives me the error "Unable to infer complex closure return type; add explicit type to disambiguate" because I'm calling arbitrary code (self.date = occurrence.start) inside ForEach{}, which isn't allowed.
实现这一点的正确方法是什么?是否有更动态的方式来执行此操作,或者我是否需要以某种方式抽象 ForEach{} 之外的代码?
What's the correct way to implement this? Is there a more dynamic way to execute this, or do I need to abstract the code outside of ForEach{} somehow?
Occurrence 对象如下所示:
The Occurrence object looks like this:
struct Occurrence: Hashable, Codable, Identifiable {
var id: Int
var title: String
var description: String
var location: String
var start: Date
var end: String
var cancelled: Bool
var public_occurrence: Bool
var created: String
var last_updated: String
private enum CodingKeys : String, CodingKey {
case id, title, description, location, start, end, cancelled, public_occurrence = "public", created, last_updated
}
}
更新:以下代码为我提供了一个字典,其中包含以相同日期为键的出现数组:
Update: The following code got me a dictionary which contains arrays of occurrences keyed by the same date:
let myDict = Dictionary( grouping: value ?? [], by: { occurrence -> String in
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .medium
dateFormatter.timeStyle = .none
return dateFormatter.string(from: occurrence.start)
})
self.userData.latestOccurrences = myDict
但是,如果我尝试在我的视图中使用它,如下所示:
However, if I try and use this in my View as follows:
ForEach(self.occurrencesByDate) { occurrenceSameDate in
// Section(header: Text("(occurrenceSameDate[0].start, formatter: Self.dateFormatter)")) {
ForEach(occurrenceSameDate, id: occurrenceSameDate.id){ occurrence in
NavigationLink(
destination: OccurrenceDetail(occurrence: occurrence)
.environmentObject(self.userData)
) {
OccurrenceRow(occurrence: occurrence)
}
}
// }
}
(当我开始工作时,部分内容已被注释掉)
(Section stuff commented out while I get the main bit working)
我收到此错误:无法将类型 '_.Element' 的值转换为预期的参数类型 'Occurrence'
I get this error: Cannot convert value of type '_.Element' to expected argument type 'Occurrence'
推荐答案
参考我对你的问题的评论,数据应该在显示之前分成几部分.
In reference to my comment on your question, the data should be put into sections before being displayed.
这个想法是有一个对象数组,其中每个对象包含一个出现的数组.所以我们简化了你的出现对象(对于这个例子)并创建了以下内容:
The idea would be to have an array of objects, where each object contains an array of occurrences. So we simplify your occurrence object (for this example) and create the following:
struct Occurrence: Identifiable {
let id = UUID()
let start: Date
let title: String
}
接下来我们需要一个对象来表示在给定日期发生的所有事件.我们将其称为 Day
对象,但名称对于本示例来说并不重要.
Next we need an object to represent all the occurrences that occur on a given day. We'll call it a Day
object, however the name is not too important for this example.
struct Day: Identifiable {
let id = UUID()
let title: String
let occurrences: [Occurrence]
let date: Date
}
所以我们要做的是将一组 Occurrence
对象转换成一组 Day
对象.
So what we have to do is take an array of Occurrence
objects and convert them into an array of Day
objects.
我创建了一个简单的结构来执行实现这一目标所需的所有任务.显然,您希望修改它以使其与您拥有的数据相匹配,但关键是您将拥有一组 Day
对象,然后您可以轻松地显示这些对象.我在代码中添加了注释,以便您可以清楚地看到每件事情在做什么.
I have created a simple struct that performs all the tasks that are needed to make this happen. Obviously you would want to modify this so that it matches the data that you have, but the crux of it is that you will have an array of Day
objects that you can then easily display. I have added comments through the code so that you can clearly see what each thing is doing.
struct EventData {
let sections: [Day]
init() {
// create some events
let first = Occurrence(start: EventData.constructDate(day: 5, month: 5, year: 2019), title: "First Event")
let second = Occurrence(start: EventData.constructDate(day: 5, month: 5, year: 2019, hour: 10), title: "Second Event")
let third = Occurrence(start: EventData.constructDate(day: 5, month: 6, year: 2019), title: "Third Event")
// Create an array of the occurrence objects and then sort them
// this makes sure that they are in ascending date order
let events = [third, first, second].sorted { $0.start < $1.start }
// create a DateFormatter
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .medium
dateFormatter.timeStyle = .none
// We use the Dictionary(grouping:) function so that all the events are
// group together, one downside of this is that the Dictionary keys may
// not be in order that we require, but we can fix that
let grouped = Dictionary(grouping: events) { (occurrence: Occurrence) -> String in
dateFormatter.string(from: occurrence.start)
}
// We now map over the dictionary and create our Day objects
// making sure to sort them on the date of the first object in the occurrences array
// You may want a protection for the date value but it would be
// unlikely that the occurrences array would be empty (but you never know)
// Then we want to sort them so that they are in the correct order
self.sections = grouped.map { day -> Day in
Day(title: day.key, occurrences: day.value, date: day.value[0].start)
}.sorted { $0.date < $1.date }
}
/// This is a helper function to quickly create dates so that this code will work. You probably don't need this in your code.
static func constructDate(day: Int, month: Int, year: Int, hour: Int = 0, minute: Int = 0) -> Date {
var dateComponents = DateComponents()
dateComponents.year = year
dateComponents.month = month
dateComponents.day = day
dateComponents.timeZone = TimeZone(abbreviation: "GMT")
dateComponents.hour = hour
dateComponents.minute = minute
// Create date from components
let userCalendar = Calendar.current // user calendar
let someDateTime = userCalendar.date(from: dateComponents)
return someDateTime!
}
}
这允许 ContentView
只是两个嵌套的 ForEach
.
This then allows the ContentView
to simply be two nested ForEach
.
struct ContentView: View {
// this mocks your data
let events = EventData()
var body: some View {
NavigationView {
List {
ForEach(events.sections) { section in
Section(header: Text(section.title)) {
ForEach(section.occurrences) { occurrence in
NavigationLink(destination: OccurrenceDetail(occurrence: occurrence)) {
OccurrenceRow(occurrence: occurrence)
}
}
}
}
}.navigationBarTitle(Text("Events"))
}
}
}
// These are sample views so that the code will work
struct OccurrenceDetail: View {
let occurrence: Occurrence
var body: some View {
Text(occurrence.title)
}
}
struct OccurrenceRow: View {
let occurrence: Occurrence
var body: some View {
Text(occurrence.title)
}
}
这是最终的结果.
这篇关于如何在 SwiftUI List/ForEach 中动态创建部分并避免“无法推断复杂的闭包返回类型"的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!