Swift ui copy and edit struct Identifiable Codable - swift

I have the following struct:
struct Note: Identifiable, Codable {
enum CodingKeys: CodingKey {
case title, message, background
}
let id = UUID()
let title: String
let message: String
let background: NoteBackground
func copy(with zone: NSZone? = nil) -> Any {
let copy = Note(title: title, message: message, background: background)
return copy
}
}
What I would like to be able to do is to be able to copy and modify an object, with an id other than the copied object.
let note = Note(
title: "Title",
message: "Message",
background: .url("https://a.wattpad.com/useravatar/climaxmite.256.718018.jpg")
)
let note2 = note
.copy()//clone
.edit(background: .gradient([
Color(red: 0, green: 0.95, blue: 1),
Color(red: 0.56, green: 0.18, blue: 0.89)
])) as! Note
How can I do any advice?

Related

How can I retrieve the color value from Firebase

How can I retrieve the color value from Firebase from String to color in swift
I am a beginner, I have managed to save the color value in Firebase
But I could not retrieve it to the application
This value is in Firebase
Color: "0.424215 0.911966 0.273487 1"
This is the code that I was able to convert the color value to string and then I set it down in Firebase
let cgColor = self.ColorIsColor!.cgColor
self.labelColor = CIColor(cgColor: cgColor).stringRepresentation
Here I want to restore color on detailLabel.textColor
class CellDetiles: UITableViewCell {
var labelColor = String()
var ColorIsColor : UIColor?
#IBOutlet weak var imageLabel: UIImageView!
#IBOutlet weak var detailLabel: UITextView!
#IBOutlet weak var qusLabel: UILabel!
var Dat : DetilesVC?
var arrQR : QRModel? {
didSet {
SetupQR()
}
}
func SetupQR () {
qusLabel.text = arrQR?.sub
detailLabel.text = arrQR?.detiles
detailLabel.textColor = ""
if let stringImage = arrQR?.ImgSub {
let image = URL(string: stringImage)
self.imageLabel.sd_setImage(with : image)
}
}
}
Please help me
What you can do is split the String by spaces and separate each component out:
let rgba = labelColor.split(separator: " ")
let r = Double(rgba[0]) ?? 0
let g = Double(rgba[1]) ?? 0
let b = Double(rgba[2]) ?? 0
let a = Double(rgba[3]) ?? 0
let finalColor = UIColor(red: CGFloat(r),
green: CGFloat(g),
blue: CGFloat(b),
alpha: CGFloat(a))
You can put this wherever you need to decode the color, but it would probably be better to put it in a UIColor initializer instead:
extension UIColor {
convenience init(string: String) {
let rgba = labelColor.split(separator: " ")
let r = Double(rgba[0]) ?? 0
let g = Double(rgba[1]) ?? 0
let b = Double(rgba[2]) ?? 0
let a = Double(rgba[3]) ?? 0
self.init(red: CGFloat(r), green: CGFloat(g), blue: CGFloat(b), alpha: CGFloat(a))
}
}
That way you can just call UIColor(string: colorString) whenever you need to decode it.
How you load the color is determined by how you store it in the first place. NSData objects are quite convenient as they can be encoded, cast to a string object etc which is easily stored in firebase.
I would suggest a simple extension to NSColor
extension NSColor {
func saveToFirebase() -> String {
let data = try! NSKeyedArchiver.archivedData(withRootObject: self, requiringSecureCoding: false)
let dataAsString = data.base64EncodedString()
return dataAsString
}
func getFromFirebase(stringData: String) -> NSColor {
let colorData = Data(base64Encoded: stringData)!
let color = try! NSKeyedUnarchiver.unarchivedObject(ofClass: NSColor.self, from: colorData)
return color!
}
}
then to save a color, do this
let red = NSColor.red
let redColorDict: [String: Any] = [
"name": "red",
"color_string": red.saveToFirebase()
]
colorsRef.child("red_color").setValue(redColorDict)
and then to read back in do this to read the red color and set the background of a view to red
func readColors() {
let redRef = self.ref.child("colors").child("red_color") //self.ref points to my firebase
redRef.observeSingleEvent(of: .value, with: { snapshot in
if let dataAsString = snapshot.childSnapshot(forPath: "color_string").value as? String {
self.myView.layer?.backgroundColor = NSColor().getFromFirebase(stringData: dataAsString).cgColor
}
})
}
Note there is NO error checking in the above for brevity so be sure to handle the optionals safely.
Also note this is macOS, but it would apply to iOS with UIColor.
func getColor(_ str: String) -> UIColor? {
let colorData = str
.components(separatedBy: .whitespaces)
.compactMap { Double($0) }
.compactMap { CGFloat($0) }
guard colorData.count == 4 else {
return nil
}
return UIColor(displayP3Red: colorData[0], green: colorData[1], blue: colorData[2], alpha: colorData[3])
}

In swift, how to convert a struct with a variable of a 2D dictionary to a saved data?

I have a struct:
struct Personne {let uuid = UUID().uuidString
var nom: String = ""
var prenom: String = ""
var age: Int = 18
var email: String = ""
var motDePass: String = ""
var genre: Genre = .Femme
enum Genre {
case Homme, Femme, Autre}
var historique: [Date: [[AlimentObject]]] = [Date(): [[], [], [], [], [], []]]
//etc ...
}
and AlimentObject struct:
struct AlimentObject : Equatable {
var nomAliment = ""
var uuid = UUID().uuidString
var poids : Float = 100
var calories : Float = 0
var proteines : Float = 0
var lipides : Float = 0
var glucides : Float = 0
}
I need to convert everything in CoreData
I have already created an NSManagedObject object of the class named AlimentObject, and another of the class Personne. The original classes will be deleted in the end, and the NSManagedObject will take their places.
The question is, how to put the variable "historique" in a saved data in order to load it, and save it when needed?
PS:
#Leo here is the function that i put in the view did load of the very first view controller:
public func sauvegarderDonneesPersonnne() {
do {
let appSupport = try FileManager.default.url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
let appSupportDirectory = appSupport.appendingPathComponent(Bundle.main.bundleIdentifier ?? Bundle.main.bundleIdentifier!, isDirectory: true)
try FileManager.default.createDirectory(at: appSupportDirectory, withIntermediateDirectories: true, attributes: nil)
let fileURL = appSupportDirectory.appendingPathComponent("Personne.json")
let loadedData = try Data(contentsOf: fileURL)
// decoding the data loaded
let loadedPersonne: Personne = try loadedData.decodedObject()
self.personne.shared = loadedPersonne
print("loaded")
} catch {
print("error")
}
}
and here is the on in the save button:
it's a simple button, where the user saves his data when he tapped it
public func sauvegarderDonneesPersonnne() {
do {
let appSupport = try FileManager.default.url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
let appSupportDirectory = appSupport.appendingPathComponent(Bundle.main.bundleIdentifier ?? Bundle.main.bundleIdentifier!, isDirectory: true)
try FileManager.default.createDirectory(at: appSupportDirectory, withIntermediateDirectories: true, attributes: nil)
let fileURL = appSupportDirectory.appendingPathComponent("Personne.json")
// encoding
let data = try personne.shared.data()
// saving
try data.write(to: fileURL, options: .atomic)
print("saved")
} catch {
print("error")
}
}
As I already mentioned in comments you can try first to encode your data using Codable protocol and write it to your application support directory:
This is how I would structure your data:
struct Personne: Codable {
enum Genre: String, Codable { case homme, femme, autre }
let uuid: String
let nom: String
let prenom: String
let age: Int
let email: String
let motDePass: String
let genre: Genre
var historique: [Date: [[AlimentObject]]] = [:]
init(uuid: String = UUID().uuidString, nom: String, prenom: String, age: Int, email: String, motDePass: String, genre: Genre, historique: [Date: [[AlimentObject]]]) {
self.uuid = uuid
self.nom = nom
self.prenom = prenom
self.age = age
self.email = email
self.motDePass = motDePass
self.genre = genre
self.historique = historique
}
}
struct AlimentObject: Codable, Equatable {
let uuid: String
let nomAliment: String
let poids: Float
let calories: Float
let proteines: Float
let lipides: Float
let glucides: Float
init(uuid: String = UUID().uuidString, nomAliment: String, poids: Float, calories: Float, proteines: Float, lipides: Float, glucides: Float) {
self.uuid = uuid
self.nomAliment = nomAliment
self.poids = poids
self.calories = calories
self.proteines = proteines
self.lipides = lipides
self.glucides = glucides
}
}
Add those helpers to your project to make it easier to encode/decode your objects:
extension JSONEncoder {
static let iso8601 = JSONEncoder(dateEncodingStrategy: .iso8601)
}
extension JSONEncoder {
convenience init(dateEncodingStrategy: DateEncodingStrategy) {
self.init()
self.dateEncodingStrategy = dateEncodingStrategy
}
}
extension Encodable {
func data(using encoder: JSONEncoder = .iso8601) throws -> Data {
try encoder.encode(self)
}
func json() throws -> String {
try String(data: data(), encoding: .utf8) ?? ""
}
}
extension JSONDecoder {
static let iso8601 = JSONDecoder(dateDecodingStrategy: .iso8601)
}
extension JSONDecoder {
convenience init(dateDecodingStrategy: DateDecodingStrategy) {
self.init()
self.dateDecodingStrategy = dateDecodingStrategy
}
}
extension Data {
func decodedObject<T: Decodable>(using decoder: JSONDecoder = .iso8601) throws -> T {
try decoder.decode(T.self, from: self)
}
}
Playground testing:
let personne: Personne = .init(nom: "nom",
prenom: "prenom",
age: 18,
email: "email",
motDePass: "motDePass",
genre: .femme,
historique:
[Date() :
[
[.init(nomAliment: "nomAlimentA", poids: 1.1, calories: 1.2, proteines: 1.3, lipides: 1.4, glucides: 1.5),
.init(nomAliment: "nomAlimentB", poids: 2.2, calories: 2.3, proteines: 2.4, lipides: 2.5, glucides: 2.6)],
[.init(nomAliment: "nomAlimentC", poids: 3.3, calories: 3.4, proteines: 3.5, lipides: 3.6, glucides: 3.7),
.init(nomAliment: "nomAlimentD", poids: 4.4, calories: 4.5, proteines: 4.6, lipides: 4.7, glucides: 4.8)]
]
])
do {
let appSupport = try FileManager.default.url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
let appSupportDirectory = appSupport.appendingPathComponent(Bundle.main.bundleIdentifier ?? "companyName", isDirectory: true)
try FileManager.default.createDirectory(at: appSupportDirectory, withIntermediateDirectories: true, attributes: nil)
let fileURL = appSupportDirectory.appendingPathComponent("Personne.json")
// encoding
let data = try personne.data()
// saving
try data.write(to: fileURL, options: .atomic)
// loading
let loadedData = try Data(contentsOf: fileURL)
// decoding the data loaded
let loadedPersonne: Personne = loadedData.decodedObject()
print("loadedPersonne", loadedPersonne)
} catch {
print(error)
}
This will print:
loadedPersonne Personne(uuid: "9E6A65EB-8F94-4CCE-B4F8-60EA79EA4D02", nom: "nom", prenom: "prenom", age: 18, email: "email", motDePass: "motDePass", genre: Personne.Genre.femme, historique: [2020-11-16 21:45:22 +0000: [[AlimentObject(uuid: "2ED19F72-52BE-4A6C-BB23-3E436BE3DEB3", nomAliment: "nomAlimentA", poids: 1.1, calories: 1.2, proteines: 1.3, lipides: 1.4, glucides: 1.5), AlimentObject(uuid: "F73812E8-BD3F-4BC3-A734-4EA9ECDEE130", nomAliment: "nomAlimentB", poids: 2.2, calories: 2.3, proteines: 2.4, lipides: 2.5, glucides: 2.6)], [AlimentObject(uuid: "C73A5162-A63E-44A3-972A-4F368385277D", nomAliment: "nomAlimentC", poids: 3.3, calories: 3.4, proteines: 3.5, lipides: 3.6, glucides: 3.7), AlimentObject(uuid: "BA3091D5-D54D-4F31-BFA0-8E74CC004F46", nomAliment: "nomAlimentD", poids: 4.4, calories: 4.5, proteines: 4.6, lipides: 4.7, glucides: 4.8)]]])

Saving UIColor within Struct Array to UserDefaults

I have tried to implement the solution by Vadian here: Make UIColor Codable
But I am getting an error that I haven't been able to overcome.
Here is my implementation of the above mention solution:
struct Color : Codable {
var red : CGFloat = 0.0, green: CGFloat = 0.0, blue: CGFloat = 0.0, alpha: CGFloat = 0.0
var uiColor : UIColor {
return UIColor(red: red, green: green, blue: blue, alpha: alpha)
}
init(uiColor : UIColor) {
uiColor.getRed(&red, green: &green, blue: &blue, alpha: &alpha)
}
}
struct Tasting: Codable {
private enum CodingKeys: String, CodingKey { case id, title, color, textColor, notes }
var id: Int
var title: String
var color : UIColor
var textColor : UIColor
var notes: String
init(id: Int, title: String, color : UIColor, textColor : UIColor, notes: String) {
self.id = id
self.title = title
self.color = color
self.textColor = textColor
self.notes = notes
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(Int.self, forKey: .id)
title = try container.decode(String.self, forKey: .title)
color = try container.decode(Color.self, forKey: .color).uiColor
textColor = try container.decode(Color.self, forKey: .textColor).uiColor
notes = try container.decode(String.self, forKey: .notes)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
try container.encode(title, forKey: .title)
try container.encode(Color(uiColor: color), forKey: .color)
try container.encode(Color(uiColor: color), forKey: .textColor)
try container.encode(notes, forKey: .notes)
}
}
And here:
//Encodes UIColor so it can be saved
let tastings = [
Tasting(id: 0, title: "(Delete this row after you add your first tasting!)", color: .green, textColor: .black, notes: "Add notes here.")
]
do {
let data = try JSONEncoder().encode(tastings)
print(String(data: data, encoding: .utf8)!)
let newTastings = try JSONDecoder().decode(Tasting.self, from: data)
print("newTastings \(newTastings)")
} catch {
print("newTastings \(error)")
}
Then saving it to UserDefaults.
//Saves new brand to device memory
let savedTastings = tastings
UserDefaults.standard.set(savedTastings, forKey: "tastings")
These are the errors I am getting:
newTastings typeMismatch(Swift.Dictionary<Swift.String, Any>, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Dictionary<String, Any> but found an array instead.", underlyingError: nil))
2019-07-25 11:38:05.909711-0700 WhiskyTasting[10601:3581697] [User Defaults] Attempt to set a non-property-list object (
"WhiskyTasting.Tasting(id: 0, title: \"(Delete this row after you add your first tasting!)\", color: UIExtendedSRGBColorSpace 0 1 0 1, textColor: UIExtendedGrayColorSpace 0 1, notes: \"Add notes here.\")"
) as an NSUserDefaults/CFPreferences value for key tastings
2019-07-25 11:38:05.910207-0700 WhiskyTasting[10601:3581697] ***
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Attempt to insert non-property list object (
"WhiskyTasting.Tasting(id: 0, title: \"(Delete this row after you add your first tasting!)\", color: UIExtendedSRGBColorSpace 0 1 0 1, textColor: UIExtendedGrayColorSpace 0 1, notes: \"Add notes here.\")"
) for key tastings'
I hope y'all see a simple typo that I'm missing. Been struggling with variations of this for a few days.
The errors are not related to my solution.
The first error tells you that the object is an array. Please read your code, tastings is clearly an array.
So you have to decode an array
let newTastings = try JSONDecoder().decode([Tasting].self, from: data)
The second error tells you that in your struct is a type which is not property list compliant. This type is UIColor. You cannot save Tasting instances to UserDefaults, but you can save JSON-/ or PropertyList-encoded Tasting instances.
let data = try JSONEncoder().encode(tastings)
UserDefaults.standard.set(data, forKey: "tastings")

Make UIColor Codable

struct Task: Codable {
var content: String
var deadline: Date
var color: UIColor
...
}
There are warnings saying "Type 'Task' does not conform to protocol 'Decodable'" and "Type 'Task' does not conform to protocol 'Encodable'". I searched and found that this is because UIColor does not conform to Codable. But I have no idea how to fix that. So...
How to make UIColor Codable?
If you care only about the 4 color components this is a simple solution using a wrapper struct
struct Color : Codable {
var red : CGFloat = 0.0, green: CGFloat = 0.0, blue: CGFloat = 0.0, alpha: CGFloat = 0.0
var uiColor : UIColor {
return UIColor(red: red, green: green, blue: blue, alpha: alpha)
}
init(uiColor : UIColor) {
uiColor.getRed(&red, green: &green, blue: &blue, alpha: &alpha)
}
}
In this case you have to write a custom initializer to convert the 4 color components from Color to UIColor and vice versa.
struct MyTask: Codable { // renamed as MyTask to avoid interference with Swift Concurrency
private enum CodingKeys: String, CodingKey { case content, deadline, color }
var content: String
var deadline: Date
var color : UIColor
init(content: String, deadline: Date, color : UIColor) {
self.content = content
self.deadline = deadline
self.color = color
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
content = try container.decode(String.self, forKey: .content)
deadline = try container.decode(Date.self, forKey: .deadline)
color = try container.decode(Color.self, forKey: .color).uiColor
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(content, forKey: .content)
try container.encode(deadline, forKey: .deadline)
try container.encode(Color(uiColor: color), forKey: .color)
}
}
Now you can encode and decode UIColor
let task = MyTask(content: "Foo", deadline: Date(), color: .orange)
do {
let data = try JSONEncoder().encode(task)
print(String(data: data, encoding: .utf8)!)
let newTask = try JSONDecoder().decode(MyTask.self, from: data)
print(newTask)
} catch { print(error) }
A smart alternative for Swift 5.1 and higher is a property wrapper
#propertyWrapper
struct CodableColor {
var wrappedValue: UIColor
}
extension CodableColor: Codable {
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let data = try container.decode(Data.self)
guard let color = try NSKeyedUnarchiver.unarchivedObject(ofClass: UIColor.self, from: data) else {
throw DecodingError.dataCorruptedError(
in: container,
debugDescription: "Invalid color"
)
}
wrappedValue = color
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
let data = try NSKeyedArchiver.archivedData(withRootObject: wrappedValue, requiringSecureCoding: true)
try container.encode(data)
}
}
and mark the property with #CodableColor
struct MyTask: Codable {
var content: String
var deadline: Date
#CodableColor var color: UIColor
...
}
Here's a solution which I've published as a Swift Package which will work for any color in any color space (even fancy system colors like label and windowBackground!), and any other NSCoding object!
It's relatively easy to use:
import SerializationTools
let color = UIColor.label
let encodedData = try color.codable.jsonData()
// UTF-8 encoded Base64 representation of the `NSCoding` data
let decodedColor = try UIColor.CodableBridge(jsonData: encodedData).value
And remember that this even works with the fancy magical colors like .label and .systemBackground!
Of course, you can also use it like any other Swift codable, such as placing it in a struct with auto-synthesized Codable conformance or using it with JSONEncoder/JSONDecoder:
import SerializationTools
struct Foo: Codable {
let color: UIColor.CodableBridge
init(color: UIColor) {
self.color = color.codable
}
}
import SerializationTools
let fooInstance = Foo(color: .systemPurple)
let encoder = JSONEncoder()
let encodedData = try encoder.encode(fooInstance)
let decoder = JSONDecoder()
let decodedFoo = try decoder.decode(Foo.self, from: encodedData)
This will work with NSColor, too, as well as anything else that conforms to NSCoding, such as NSImage/UIImage, MKMapView, GKAchievement, and much more!
I use UIColor subclass
final class Color: UIColor, Decodable {
convenience init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let hexString = try container.decode(String.self)
self.init(hex: hexString)
}
}
Thus, there is no need for each class or structure to implement the functions of the Decodable protocol. It seems to me that this is the most convenient way, especially when there can be many color parameters in one class or structure.
You can implement Encodable in the same way if it's necessary.
I solved this issue with a custom class that allowed automatic conformance to codable. This is beneficial as it prevents writing custom conformance to codable. It also makes it easier to work with UIColor and and CGColor
class Color:Codable{
private var _green:CGFloat
private var _blue:CGFloat
private var _red:CGFloat
private var alpha:CGFloat
init(color:UIColor) {
color.getRed(&_red, green: &_green, blue: &_blue, alpha: &alpha)
}
var color:UIColor{
get{
return UIColor(red: _red, green: _green, blue: _blue, alpha: alpha)
}
set{
newValue.getRed(&_red, green:&_green, blue: &_blue, alpha:&alpha)
}
}
var cgColor:CGColor{
get{
return color.cgColor
}
set{
UIColor(cgColor: newValue).getRed(&_red, green:&_green, blue: &_blue, alpha:&alpha)
}
}
}
We can make UIColor and all of its descendants Codable.
import UIKit
extension Decodable where Self: UIColor {
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let components = try container.decode([CGFloat].self)
self = Self.init(red: components[0], green: components[1], blue: components[2], alpha: components[3])
}
}
extension Encodable where Self: UIColor {
public func encode(to encoder: Encoder) throws {
var r, g, b, a: CGFloat
(r, g, b, a) = (0, 0, 0, 0)
var container = encoder.singleValueContainer()
self.getRed(&r, green: &g, blue: &b, alpha: &a)
try container.encode([r,g,b,a])
}
}
extension UIColor: Codable { }
Check it
import XCTest
class ColorDescendant: UIColor { }
let testColor = ColorDescendant.green
class CodingTextCase: XCTestCase {
let encoder = JSONEncoder()
let decoder = JSONDecoder()
func testUIColor() throws {
let colorAsJSON = try encoder.encode(UIColor.red)
print(String(data: colorAsJSON, encoding: .utf8)!)
let uiColor = try? decoder.decode(UIColor.self, from: colorAsJSON)
XCTAssertEqual(uiColor!, UIColor.red)
}
func testUIColorDescendant() throws {
let colorAsJSON = try encoder.encode(testColor)
print(String(data: colorAsJSON, encoding: .utf8)!)
let uiColor = try? decoder.decode(ColorDescendant.self, from: colorAsJSON)
XCTAssertEqual(uiColor!, testColor)
}
}
CodingTextCase.defaultTestSuite.run()
This solution requires only 9 bytes for data storage while more generalized one will require about 500 bytes.

swift UIColor extension of "cannot assign to value: 'self' is immutable"

I wrote an extension of UIColor to change alpha directly:
public extension UIColor {
public var rgbaComponents: (red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat) {
var components: [CGFloat] {
let c = cgColor.components!
if c.count == 4 {
return c
}
return [c[0], c[0], c[0], c[1]]
}
let r = components[0]
let g = components[1]
let b = components[2]
let a = components[3]
return (red: r, green: g, blue: b, alpha: a)
}
public var alpha: CGFloat {
get {
return cgColor.alpha
}
set {
var rgba = rgbaComponents
self = UIColor(red: rgba.red // error here: "cannot assign to value: 'self' is immutable"
, green: rgba.green, blue: rgba.blue, alpha: newValue)
}
}
}
but there is an error:
cannot assign to value: 'self' is immutable
But extension of Date to assign to self is OK
public extension Date {
public var day: Int {
get {
return Calendar.current.component(.day, from: self)
}
set {
let allowedRange = Calendar.current.range(of: .day, in: .month, for: self)!
guard allowedRange.contains(newValue) else { return }
let currentDay = Calendar.current.component(.day, from: self)
let daysToAdd = newValue - currentDay
if let date = Calendar.current.date(byAdding: .day, value: daysToAdd, to: self) {
self = date // This is OK
}
}
}
}
Is it because of UIColor is from NSObject and Date is a Swift struct? What is the root cause?
You're correct it is because Date is a struct (value type) and UIColor is a class (reference type).
When you assign to self for a struct you are really just updating all the properties of that structure (simply put) so the actual memory location doesn't change. So you are not actually mutating the value of self itself.
However when you assign to self for a class you are creating an entire new class in memory so when you assign it to self you are trying to mutate self. Even if it was allowed how would anything holding a reference to the colour handle it as they would still be holding a reference to the original class.