Years ago there was this iPad app called “The Best Ceasar”, some bloke thought it was a good idea to make one whole app just about one recipe… he was right. The app is long gone but here is the gist of the recipe as far as I remember it.
Dressing
Ingredients
Anchovies
Garlic
Worcestershire Sauce
Red Wine Vinegar
Olive Oil
Parmesan Cheese
Salt
Pepper
Preparation
Smash the anchovies in a bowl into a fine paste.
Press in 3 cloves of garlic.
Add a dash of olive oil (depending on how oily your anchovies are).
Add a table spoon of Worcestershire sauce.
Add a table spoon of red wine vinegar.
Add salt and pepper to taste.
Mix it all together.
Add in parmesan to obtain a thick-ish dressing.
Croutons
Ingredients
Bread
Olive Oil
Garlic
Preparation
My current go-to recipe is to:
Press garlic into a bowl of olive oil.
Baste bread slices with the garlic oil.
Put the slices in an oven until crispy.
Cut the toasted bread into croutons.
Chicken
Ingredients
Chicken Breast
Salt
Pepper
Olive Oil
Preparation
If the chicken breast is thicker than about 15mm, cut it in half (to make it thinner).
Pre-heat the oven to 200ºC (or 180 with fan on).
Salt and pepper the chicken.
Put some olive oil in a pan and heat it at high temperature.
Sear the chicken on all sides until golden on the outside.
Put the chicken in the oven, cook until the inner temperature is a bit under 75ºC.
Dice the chicken.
Serving
Wash and dry (as much as you can) a couple of heads of romaine lettuce. Add the dressing and toss it properly. Mix in the chicken and croutons, toss it again, sprinkle with more grated parmesan.
The Apple Watch does not display its local IP address in the settings for some
reason. Here is one way to get it, if you have a second Mac on the network.
Open terminal on the mac, run python -m SimpleHTTPServer.
Get your Mac’s local IP address (option+click on the network icon in the
toolbar).
Send yourself an iMessage with text http://YOUR_MACS_IP:8000.
Turn WiFi off on your iPhone. This is important because the Apple Watch
can use your phone’s WiFi.
Open Messages on your watch, find the message, tap on the link.
In the terminal on your Mac you will see something like
192.168.0.21 - - [12/Oct/2021 12:00:00] "GET / HTTP/1.1" 200 -.
The task: Store colors (Color, UIColor and CGColor) in Core Data, while remaining 100% compatible with the SwiftUI color picker.
Setting Up the Core Data Model
In your model add an attribute to your entity and set its type to Transformable. Select it and in the Data Model Inspector (right pane in Xcode, last tab) set the Transformer to “SerializableColorTransformer” and Custom Class to “SerializableColor”.
These two strings are magic values, and I’ll explain their origin later.
The whole thing should look like this:
Code Implementation
Core Data has several requirements on what can be stored. It has to be NSCoding or NSSecureCoding. This in turn means that it has to be an Obj-C class. This rules out both CGColor (a non-Obj-c class) and Color (a struct).
In my case I don’t care about color spaces other than P3 and sRGB. Let us create a class that stores red, green, blue and alpha components and a color space. The class will transform any color space other than P3 into sRGB.
Here is the whole listing for the class, which I called SerializableColor. This is where the magic string for Custom Class in the previous section comes from.
// SerializableColor.swiftimport Foundation
import struct CoreGraphics.CGFloat
import class CoreGraphics.CGColor
import class CoreGraphics.CGColorSpace
import class UIKit.UIColor
import struct SwiftUI.Color
publicclass SerializableColor: NSObject, NSCoding, NSSecureCoding {
publicstaticvar supportsSecureCoding: Bool = truepublicenum SerializableColorSpace: Int {
case sRGB = 0case displayP3 = 1 }
let colorSpace: SerializableColorSpace
let r: Floatlet g: Floatlet b: Floatlet a: Floatpublicfuncencode(with coder: NSCoder) {
coder.encode(colorSpace.rawValue, forKey: "colorSpace")
coder.encode(r, forKey: "red")
coder.encode(g, forKey: "green")
coder.encode(b, forKey: "blue")
coder.encode(a, forKey: "alpha")
}
requiredpublicinit?(coder: NSCoder) {
colorSpace = SerializableColorSpace(rawValue: coder.decodeInteger(forKey: "colorSpace")) ?? .sRGB
r = coder.decodeFloat(forKey: "red")
g = coder.decodeFloat(forKey: "green")
b = coder.decodeFloat(forKey: "blue")
a = coder.decodeFloat(forKey: "alpha")
}
init(colorSpace: SerializableColorSpace, red: Float, green: Float, blue: Float, alpha: Float) {
self.colorSpace = colorSpace
self.r = red
self.g = green
self.b = blue
self.a = alpha
}
convenienceinit(from cgColor: CGColor) {
var colorSpace: SerializableColorSpace = .sRGB
var components: [Float] = [0, 0, 0, 0]
// Transform the color into sRGB spaceif cgColor.colorSpace?.name == CGColorSpace.displayP3 {
iflet p3components = cgColor.components?.map({ Float($0) }),
cgColor.numberOfComponents == 4 {
colorSpace = .displayP3
components = p3components
}
} else {
iflet sRGB = CGColorSpace(name: CGColorSpace.sRGB),
let sRGBColor = cgColor.converted(to: sRGB, intent: .defaultIntent, options: nil),
let sRGBcomponents = sRGBColor.components?.map({ Float($0) }),
sRGBColor.numberOfComponents == 4 {
components = sRGBcomponents
}
}
self.init(colorSpace: colorSpace, red: components[0], green: components[1], blue: components[2], alpha: components[3])
}
convenienceinit(from color: Color) {
self.init(from: UIColor(color))
}
convenienceinit(from uiColor: UIColor) {
self.init(from: uiColor.cgColor)
}
var cgColor: CGColor {
return uiColor.cgColor
}
var color: Color {
return Color(self.uiColor)
}
var uiColor: UIColor {
if colorSpace == .displayP3 {
return UIColor(displayP3Red: CGFloat(r), green: CGFloat(g), blue: CGFloat(b), alpha: CGFloat(a))
} else {
return UIColor(red: CGFloat(r), green: CGFloat(g), blue: CGFloat(b), alpha: CGFloat(a))
}
}
}
// MARK: Transformer Class// For CoreData compatibility.@objc(SerializableColorTransformer)
class SerializableColorTransformer: NSSecureUnarchiveFromDataTransformer {
overrideclass var allowedTopLevelClasses: [AnyClass] {
returnsuper.allowedTopLevelClasses + [SerializableColor.self]
}
publicoverrideclass func allowsReverseTransformation() -> Bool {
returntrue }
publicoverridefunctransformedValue(_ value: Any?) -> Any? {
guardlet data = value as? Data else {returnnil}
returntry! NSKeyedUnarchiver.unarchivedObject(ofClass: SerializableColor.self, from: data)
}
publicoverridefuncreverseTransformedValue(_ value: Any?) -> Any? {
guardlet color = value as? SerializableColor else {returnnil}
returntry! NSKeyedArchiver.archivedData(withRootObject: color, requiringSecureCoding: true)
}
}
The important parts are described as follows.
To register the transformer (explained below), add this line to the init() method of your @main. For example
The rawValue is where the Transformer magic string in the model come from.
Deeper dive
Instead of commenting the file excessively, here are explanations of details.
NSCoding inheritance
For this we implement encode(with coder: NSCoder) and init?(coder: NSCoder). These methods will enable the class to be serialized and deserialized using any standard decoder/encoder.
Note: As you can see this class will interpret any unknown color space as sRGB, this means that this is not backwards compatible.
Color space transformations
These happen in two places. When encoding the value, the class always passes through CGColor as this is the only color type that can be split into components.
Note: In init from Color I bounce through init from UIColor, this is because weirdly Color.blue.cgColor == nil but UIColor(Color.blue).cgColor has a value.
For deserialization I always pass through UIColor as this type has convenience initializers for both sRGB and P3 colorspaces.
Transformer
Core Data can write only a few basic types, including NSData. Transformers are classes that can be registered for specific types and transform them into, and from NSData.
In order to implement a transformer you need to do three things:
Write a class which inherits from NSSecureUnarchiveFromDataTransformer and overrides the allowedTopLevelClasses class variable adding your class type to the list
Override the transformedValue and reverseTransformedValue methods. These are named as seen from CoreData perspective.
transformedValue transforms NSData into your class, in this case SerializableColor.
reverseTransformedValue transforms the class into NSData. You do not need to implement this method if you are never writing to CoreData yourself. In this case change the return value of allowsReverseTransformation to false.