本文介绍了如何在SwiftUI列表/ForEach中动态创建节并避免“无法推断复杂的闭包返回类型".的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试重新创建与库存的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 = petition.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?

出现对象看起来像这样:

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列表/ForEach中动态创建节并避免“无法推断复杂的闭包返回类型".的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

10-26 22:59