Hi I'm building on an application that gets events from json file sample like this:
[{"title": "Evening Picnic", "start": "November 10, 2018 6:00 PM", "end": "November 10, 2018 7:00 PM"}]
and after it parse the events it has to sort them in order by date and group them by date as well in List Section
also checks for conflicts between events and changes the row color to red for example.
I made a simple UI, a list with dynamic sections and the events sorted inside, but for some reason the app compiles with no errors or crashes but the view is blank, like the data weren't loaded to the view.
Here is my code so far:
Events.swift
public struct Event: Codable, IntervalProtocol, Hashable, Identifiable {
public var id = UUID()
var title: String
var start: String
var end: String
}
struct AppConstants {
static let mockDataFilename = "mock"
static let jsonDataFormat = "MMMM d, yyyy h:mm a"
static let sectionDateFormat = "MMMM d, yyyy"
static let eventDateFormat = "h:mm a"
}
extension Event {
public static var jsonDateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateFormat = AppConstants.jsonDataFormat
return formatter
}()
// Parse dates into Struct variables.
var startDate: Date {
guard let date = Event.jsonDateFormatter.date(from: self.start) else {
fatalError("Unable to parse date.")
}
return date
}
var endDate: Date {
guard let date = Event.jsonDateFormatter.date(from: self.end) else {
fatalError("Unable to parse date.")
}
return date
}
var interval: Interval {
return Interval(self.startDate.timeIntervalSince1970, self.endDate.timeIntervalSince1970)
}
}
ViewModel.swift
class ViewModel: ObservableObject {
let queue = DispatchQueue(label: "com.elbeheiry")
@Published var sections = [Date: [Event]]()
@Published var sortedDays = [Date]()
@Published var intervalTree: IntervalTree!
func getEvents() {
let events = Bundle.main.eventJsonData(fileName: AppConstants.mockDataFilename)
self.intervalTree = IntervalTree(events ?? [])
let inOrder = self.intervalTree.inOrder.map { $0 as! Event }
self.buildSections(inOrder)
}
func buildSections(_ events: [Event]) {
for event in events {
let startTimestamp = Date(timeIntervalSince1970: event.interval.min)
let strippedDate = Calendar.current.dateComponents([.year, .month, .day],
from: startTimestamp)
guard let date = Calendar.current.date(from: strippedDate) else {
fatalError("Failed to remove time from Date object")
}
self.sections[date, default: []].append(event)
}
self.sortedDays = self.sections.keys.sorted()
}
}
public extension Bundle {
func eventJsonData(fileName: String) -> [Event]? {
guard let url = self.url(forResource: fileName, withExtension: "json") else {
fatalError("File was not reachable.")
}
guard let data = try? Data(contentsOf: url) else {
fatalError("File was not readable.")
}
return try? JSONDecoder().decode([Event].self, from: data)
}
}
ContentView.swift
struct ContentView: View {
@ObservedObject var viewModel: ViewModel = ViewModel()
var body: some View {
VStack {
List {
ForEach(viewModel.sortedDays, id: \.self) { header in
Section(header: Text(header, style: .date)) {
ForEach(viewModel.sections[header]!) { event in
EventCell(event: event)
.background(Color(self.viewModel.intervalTree.isConflicted(event) ? #colorLiteral(red: 0.8078431487, green: 0.02745098062, blue: 0.3333333433, alpha: 1) : #colorLiteral(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0)))
}
}
}
}
}
.onAppear(perform: {
viewModel.getEvents()
})
}
}
It's because you are using Codable
, and it is trying to decode id
from the JSON. The id
isn't in your JSON, and I can see you want each new instance to create its own ID.
If you printed an error in a catch statement, by using do-try-catch
rather than try?
, you would have seen the following:
keyNotFound(CodingKeys(stringValue: "id", intValue: nil), Swift.DecodingError.Context(codingPath: [_JSONKey(stringValue: "Index 0", intValue: 0)], debugDescription: "No value associated with key CodingKeys(stringValue: "id", intValue: nil) ("id").", underlyingError: nil))
Basically, they is no value associated with the key id
, because the key is not in the JSON.
You need to create custom CodingKey
s to exclude id
from being encoded/decoded:
public struct Event: Codable, Hashable, Identifiable {
private enum CodingKeys: CodingKey {
case title
case start
case end
}
public var id = UUID()
var title: String
var start: String
var end: String
}