The file gets parsed with JSONDecoder() and the objects are loaded into a variable anArray:[Custom]. So far, all is fine. Then, I would like to overwrite the timeStamp property of each object with this function:
convert date string to time stamp
func addTimestamps(_ anArray:[Custom]) -> [Custom] {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "dd.mm.yyyy"
anArray.forEach { (object) in
let date = dateFormatter.date(from:object.date)
let dateFromString = date!.timeIntervalSince1970
object.timeStamp = dateFromString // "Cannot assign to property: 'object' is a 'let' constant"
}
return anArray
}
But Xcode returns a Swift Compile Error "Cannot assign to property: 'object' is a 'let' constant".
Why is object a let constant? Every object is of a Custom type, which I have defined a struct for, and every property I have defined with var. So I don't understand why I cannot assign a new value to a property (timeStamp or any other) of anArray. The code works fine when applied to an array literal for testing. What's different here?
The variables in your function are automatically let constants:
func addTimestamps(_ anArray:[Custom]) -> [Custom] {
// ^^^^^^^ this is a let
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "dd.mm.yyyy"
anArray.forEach { (object) in
// ^^^^^^ this is also a let
let date = dateFormatter.date(from:object.date)
let dateFromString = date!.timeIntervalSince1970
object.timeStamp = dateFromString // "Cannot assign to property: 'object' is a 'let' constant"
}
return anArray
}
I'd rewrite this use map:
func addTimestamps(_ anArray:[Custom]) -> [Custom] {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "dd.mm.yyyy"
return anArray.map { object in
var copy = object
copy.timeStamp = dateFormatter.date(from: object.date)!.timeIntervalSince1970
return copy
}
}
Actually, I wouldn't try to coerce timeStampinto a formatted string at all; I would store it as a Date and only rendering using a foratter when it actually needed displaying.
Since the JSON file doesn't have real timestamps you should probably ignore the timestamp from the file. I would set the timestamp in the init(from: Decoder) method for your struct. You probably don't have an init method, relying on the Decoder to all the work for you but it's simple enough to write for a struct like this.
That is not what the function is doing. The function should read from the date property (a String given as "dd.mm.yyyy") and overwrite the timeStamp property (a TimeInterval).
struct Custom: Hashable, Codable, Identifiable {
//let id = UUID()
let id: Int
let date: String
let year: Int
let month: Int
let day: Int
var recipient: String
var description: String
var amount: Float
var category: String
var timeStamp: TimeInterval
}
My intention is to have a variable to sort by. I want to sort by date and use the timeStamp variable for that.
Back to the problem – my mistake was that I was trying to alter the function parameter. OK, looking at the code by @mayoff, it was new to me that the array that .map is used on can accept transformed copies of its elements rather than transformations of its own elements.
I tried the following, thinking that copying anArray first, avoids the problem of trying to alter the parameter array handed to the function. But again, I cannot assign to the timeStamp property in the .map closure. Can you explain why not, @mayoff?
func addTimestamps(_ anArray:[Custom]) -> [Custom] {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "dd.mm.yyyy"
let copy = anArray
return copy.map { object in
object.timeStamp = dateFormatter.date(from: object.date)!.timeIntervalSince1970
}
}
To load the JSON data into an anArray, I am using the same code that Apple supplies in the Building Lists and Navigation tutorial.
The idea to let the objects have an initial time stamp to overwrite later came after I failed to create an init in the struct together with a default value for timeStamp in the struct. I tried the following, after reading Working with JSON in Swift, but that returns a runtime error.
init extension
extension Custom {
init?(json: [String: Any]) {
guard
let id = json["id"] as? Int,
let date = json["date"] as? String,
let year = json["year"] as? Int,
let month = json["month"] as? Int,
let day = json["day"] as? Int,
let recipient = json["recipient"] as? String,
let description = json["description"] as? String,
let amount = json["amount"] as? Float,
let category = json["category"] as? String
else {
return nil
}
self.id = id
self.date = date
self.year = year
self.month = month
self.day = day
self.recipient = recipient
self.description = description
self.amount = amount
self.category = category
}
}
Can you recommend a source to read up on init(from: Decoder)?
You seem somewhat confused about struct. If say, you have an Int and want to mutate only the third bit, you’re still essentially mutating the entire Int.
The same goes for struct. Even if you only mutate timeStamp, you’re essentially mutating the entire struct.
That’s why when you try to mutate object.timeStamp, not only you need timeStamp to be mutable (var), but also object.
The compiler is telling you that object is not mutable (because it’s a non-inout function parameter).
You can create a mutable copy of object by writing
var newVariable = object
You can even use the same name, which is a common shadowing is such scenario
var object = object
Keep in mind that this is a copy and has nothing to do with the original object.
Then you can mutate the newVariable and return it from the map.
It is, alas, worse than that. If you want to guarantee a fixed-format date, you have to pin the locale to en_US_POSIX. See QA1480 NSDateFormatter and Internet Dates for the background on this.
Thanks. I read about the POSIX locale under "Working With Fixed Format Date Representations" in the Developer Documentation for DateFormatter. That page also includes the link you gave. Accordingly, I added these two lines: