How to dynamically create sections in a SwiftUI List/ForEach and avoid Unable to infer complex closure return type
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
}
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
}
So what we have to do is take an array of Occurrence
objects and convert them into an array of Day
objects.
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!
}
}
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)
}
}
This is the end result.
SwiftUI Conditional List - Unable to infer complex closure return type
List expects a View returned for every item, using Group like that is a bit of a hack.
Better to move the logic out of the List, either
var filteredTasks: [Task] {
return tasks.filter { !self.toggleIsOn || !$0.completed.status }
}
...
List(filteredTasks) { task in
TaskRowView(task)
}
or
List(tasks.filter( { !self.toggleIsOn || !$0.completed.status })) { task in
TaskRowView(task)
}
How to create multiple sections based on array of objects with date?
To use in a ForEach your data should be Identifiable
or there should be some way you can specify id.
struct ContentView: View {
var registrants:[Registration] = [Registration(name:"Joe", date:"12/15/2020"),Registration(name:"Billy", date:"11/12/2020"),Registration(name:"Cameron", date:"11/10/2020")]
var body: some View {
List {
ForEach(registrants) { (registration) in //<=Here
Section(header: Text(registration.date)) {
ForEach(0 ..< 5) { item in
Text("item \(item)")
}
}
}
}
}
}
struct Registration : Identifiable{ //<= here
var id = UUID()
var name: String
var date: String
}
Unable to infer complex closure return type with SwiftUI
The issue is not with the closure, but with the isFavorite
property on landmark.
It is not declared on the Landmark
type, and the compiler instead of showing the undeclared property error, unable to resolve the stacks build closure return type, so it shows and error there.
Great job Apple tutorial writers and even better one Xcode11 compiler.
To fix:
- Declare
isFavorite
variable on theLandmark
type. - Make sure you update the
landmarkData.json
for every landmark record with theisFavorite = false
entry, otherwise the app will crash at runtime.
Unable to infer complex closure return type in capture list with multiple arguments
You need to provide an argument to the closure. It can just be _ in
:
init() {
let food = Food()
let index = 0
food.foodWasEaten = { [weak self, weak food, index] _ in
if let self = self,
let food = food {
self.foodWasEaten(food: food, index: index)
}
}
}
Alternatively, if you don't need a parameter in your closure, you can change the type of your closure:
class Food {
var foodWasEaten: (() -> ())?
}
Then you can use your original approach:
food.foodWasEaten = { [weak self, weak food, index] in ...
SwiftUI Nested ForEach causes unexpected ordering
It fails due to non-unique identifiers for rows. It is used index, but it is repeated in different sections, so List row reuse caching engine confused.
In the described scenario it should be selected something unique for items that are shown in rows, like (scratchy)
ForEach(self.game.rounds) { round in // make round identifiable
// ...
ForEach(self.game.players) { player in // make player identifiable
and rounds
ids should not overlap with player
ids as well.
Related Topics
How to Do Transforms on a Calayer
How to Integrate Nsurlconnection with Uiprogressview
"Could Not Find Any Information for Class Named Viewcontroller"
Should I Git Ignore Xcodeproject/Project.Pbxproj File
Generating an Unsigned IPA iOS Application
Swift - How to Get Last Taken 3 Photos from Photo Library
Behavior Differences Between Performblock: and Performblockandwait:
Error Appstore Connect:Missing Purpose String in Info.Plist File
Uipopovercontroller Dealloc Reached While Popover Is Still Visible
How to Make Both Header and Footer in Collection View with Swift
How to Check If iOS App Is in Background
Left-Align Image and Center Text on Uibutton
How to Add a Uitoolbar Programmatically to an iOS App