Reputation: 21
I am quite new with swiftUi and SwiftData. And also English is not my first language... I will try my best !
I am trying to get a date from a string using swiftData
This is the error that is giving me:
request failed dataCorrupted(Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "items", intValue: nil), _CodingKey(stringValue: "Index 8", intValue: 8), CodingKeys(stringValue: "volumeInfo", intValue: nil), CodingKeys(stringValue: "publishedDate", intValue: nil)], debugDescription: "Date string does not match format expected by formatter.", underlyingError: nil))
I am following this tutorial (although it is not with swiftData)l: https://www.hackingwithswift.com/books/ios-swiftui/formatting-our-mission-view
This is how the date looks like in google books api: "publishedDate": "2016-02-07"
1A.-to get the date from a string, I have started adding this after let decoder = JSONDecoder() :
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
decoder.dateDecodingStrategy = .formatted(formatter)
1B.- Then (in the model) I have changed:
var publishedDate:String?
for
var publishedDate: Date?
and change it also in the init, the required init and in the encode function
So, it looks like this:
@Model
class VolumeInfo: Codable {
enum CodingKeys: String, CodingKey {
case title, subtitle, authors, publisher, publishedDate, formattedPublishedDate, summary = "description", pageCount, printType, averageRating, ratingsCount, categories, imageLinks, language
}
var title: String
var subtitle: String?
var authors: [String]?
var publisher: String?
var publishedDate: Date?
var summary: String?
var pageCount: Int?
var printType: String?
var averageRating: Float?
var ratingsCount: Int?
var categories: [String]?
var imageLinks: ImageLinks?
var language: String?
init(
title: String,
subtitle: String? = nil,
authors: [String]? = nil,
publisher: String? = nil,
publishedDate: Date? = Date.now,
summary: String? = nil,
pageCount: Int? = nil,
printType: String? = nil,
averageRating: Float? = nil,
ratingsCount: Int? = nil,
categories: [String]? = nil,
imageLinks: ImageLinks? = nil,
language: String? = nil
) {
self.title = title
self.subtitle = subtitle
self.authors = authors
self.publisher = publisher
self.publishedDate = publishedDate
self.summary = summary
self.pageCount = pageCount
self.printType = printType
self.averageRating = averageRating
self.ratingsCount = ratingsCount
self.categories = categories
self.imageLinks = imageLinks
self.language = language
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
title = try container.decode(String.self, forKey: .title)
subtitle = try container.decodeIfPresent(String.self, forKey: .subtitle)
authors = try container.decodeIfPresent([String].self, forKey: .authors)
publisher = try container.decodeIfPresent(String.self, forKey: .publisher)
publishedDate = try container.decodeIfPresent(Date.self, forKey: .publishedDate)
summary = try container.decodeIfPresent(String.self, forKey: .summary)
pageCount = try container.decodeIfPresent(Int.self, forKey: .pageCount)
printType = try container.decodeIfPresent(String.self, forKey: .printType)
averageRating = try container.decodeIfPresent(Float.self, forKey: .averageRating)
ratingsCount = try container.decodeIfPresent(Int.self, forKey: .ratingsCount)
categories = try container.decodeIfPresent([String].self, forKey: .categories)
imageLinks = try container.decodeIfPresent(ImageLinks.self, forKey: .imageLinks)
language = try container.decodeIfPresent(String.self, forKey: .language)
}
func encode(to encoder: any Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(title, forKey: .title)
try container.encodeIfPresent(subtitle, forKey: .subtitle)
try container.encodeIfPresent(authors, forKey: .authors)
try container.encodeIfPresent(publisher, forKey: .publisher)
try container.encodeIfPresent(publishedDate, forKey: .publishedDate)
try container.encodeIfPresent(summary, forKey: .summary)
try container.encodeIfPresent(pageCount, forKey: .pageCount)
try container.encodeIfPresent(printType, forKey: .printType)
try container.encodeIfPresent(averageRating, forKey: .averageRating)
try container.encodeIfPresent(ratingsCount, forKey: .ratingsCount)
try container.encodeIfPresent(categories, forKey: .categories)
try container.encodeIfPresent(imageLinks, forKey: .imageLinks)
try container.encodeIfPresent(language, forKey: .language)
}
}
2.- To get the string from the date and to be able to use it in my views, I have added a computed property to my model (as in Paul's example)l, adding it to my enum CodingKeys and to the func decode
var formattedPublishedDate: String {
publishedDate?.formatted(date: .abbreviated, time: .omitted) ?? "N/A"
}
So, the result is this:
@Model
class VolumeInfo: Codable {
enum CodingKeys: String, CodingKey {
case title, subtitle, authors, publisher, publishedDate, formattedPublishedDate, summary = "description", pageCount, printType, averageRating, ratingsCount, categories, imageLinks, language
}
var title: String
var subtitle: String?
var authors: [String]?
var publisher: String?
var publishedDate: Date?
var formattedPublishedDate: String {
publishedDate?.formatted(date: .abbreviated, time: .omitted) ?? "N/A"
}
var summary: String?
var pageCount: Int?
var printType: String?
var averageRating: Float?
var ratingsCount: Int?
var categories: [String]?
var imageLinks: ImageLinks?
var language: String?
init(
title: String,
subtitle: String? = nil,
authors: [String]? = nil,
publisher: String? = nil,
publishedDate: Date? = Date.now,
summary: String? = nil,
pageCount: Int? = nil,
printType: String? = nil,
averageRating: Float? = nil,
ratingsCount: Int? = nil,
categories: [String]? = nil,
imageLinks: ImageLinks? = nil,
language: String? = nil
) {
self.title = title
self.subtitle = subtitle
self.authors = authors
self.publisher = publisher
self.publishedDate = publishedDate
self.summary = summary
self.pageCount = pageCount
self.printType = printType
self.averageRating = averageRating
self.ratingsCount = ratingsCount
self.categories = categories
self.imageLinks = imageLinks
self.language = language
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
title = try container.decode(String.self, forKey: .title)
subtitle = try container.decodeIfPresent(String.self, forKey: .subtitle)
authors = try container.decodeIfPresent([String].self, forKey: .authors)
publisher = try container.decodeIfPresent(String.self, forKey: .publisher)
publishedDate = try container.decodeIfPresent(Date.self, forKey: .publishedDate)
summary = try container.decodeIfPresent(String.self, forKey: .summary)
pageCount = try container.decodeIfPresent(Int.self, forKey: .pageCount)
printType = try container.decodeIfPresent(String.self, forKey: .printType)
averageRating = try container.decodeIfPresent(Float.self, forKey: .averageRating)
ratingsCount = try container.decodeIfPresent(Int.self, forKey: .ratingsCount)
categories = try container.decodeIfPresent([String].self, forKey: .categories)
imageLinks = try container.decodeIfPresent(ImageLinks.self, forKey: .imageLinks)
language = try container.decodeIfPresent(String.self, forKey: .language)
}
func encode(to encoder: any Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(title, forKey: .title)
try container.encodeIfPresent(subtitle, forKey: .subtitle)
try container.encodeIfPresent(authors, forKey: .authors)
try container.encodeIfPresent(publisher, forKey: .publisher)
try container.encodeIfPresent(publishedDate, forKey: .publishedDate)
try container.encodeIfPresent(formattedPublishedDate, forKey: .formattedPublishedDate)
try container.encodeIfPresent(summary, forKey: .summary)
try container.encodeIfPresent(pageCount, forKey: .pageCount)
try container.encodeIfPresent(printType, forKey: .printType)
try container.encodeIfPresent(averageRating, forKey: .averageRating)
try container.encodeIfPresent(ratingsCount, forKey: .ratingsCount)
try container.encodeIfPresent(categories, forKey: .categories)
try container.encodeIfPresent(imageLinks, forKey: .imageLinks)
try container.encodeIfPresent(language, forKey: .language)
}
}
this the first time that I try to use a date from a string using swiftData... I am quite new learning swiftData...
Can someone please help me? how could I solve that error?
Upvotes: 2
Views: 123
Reputation: 36792
It seems that the Google dates data is not very consistent. As mentioned, in my tests I came across these variations: "publishedDate": "2015-09" and "2021-2-28".
So to cater for multiple date formats, you could try using this decoding approach where you test the different date formats (parseDate) as shown in this example code:
@Model
class VolumeInfo: Codable {
// ...
init(...)
func parseDate(from string: String) -> Date? {
let formats = ["yyyy-M-d", "yyyy-M", "yyyy-MM-d", "yyyy-MM", "yyyy-MM-dd"]
let dateFormatter = DateFormatter()
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
for format in formats {
dateFormatter.dateFormat = format
if let date = dateFormatter.date(from: string) {
return date
}
}
return nil
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
// ...
publishedDate = nil
if let dateString = try container.decodeIfPresent(String.self, forKey: .publishedDate) {
publishedDate = parseDate(from: dateString)
}
// ...
}
// ...
}
Upvotes: 1