In this episode, I'd like to add the ability to assign a color to a category. This makes it easier to visualize which category a note belongs to.
Before We Start
I've already laid the groundwork for this feature. Let me show you what we start with.
The category view controller contains a view for displaying the color of the category. Tapping the color view takes the user to the color view controller, the view controller responsible for picking a color.

To pick a color, the user needs to adjust three sliders in the color view controller. Because the color view controller is a component that we might want to reuse, it doesn't keep a reference to a category. We use a delegate protocol to notify the category view controller which color the user has picked.

Updating the Data Model
To associate a color with a category, we need to add an attribute to the Category entity in the data model. Open Notes.xcdatamodeld and add an attribute. We name the attribute colorAsHex because we'll be storing the color of a category as a hex value. The attribute is of type String.

This is a personal choice. I like this approach because it's easier to read and it results in minimal overhead in terms of converting the value to a UIColor instance. Keep in mind that it isn't possible to store UIColor instances in the persistent store, we always need to convert it to another value, a String in this example.
We could also convert it to binary data. That's another common option. The downside is that it's harder to debug since most developers cannot read binary data ... or at least I can't.
Before we move on, I want to make two changes to the attributes of the colorAsHex attribute. With the colorAsHex attribute selected, open the Data Model Inspector on the right and uncheck the Optional checkbox.

Remember that this makes the attribute required. It means a category cannot be stored in the persistent store if it doesn't have a value for the colorAsHex attribute. This is fine because we're going to make another change.
The Data Model Inspector also lets us define a default value for an attribute. What does that mean? If we don't explicitly assign a value to the colorAsHex attribute, Core Data sets the value of the attribute to the value of the Default Value field. Because we're working with hex values, that's very easy to do. In the Default Value field, enter the hex value for white, FFFFFF. That's another benefit of working with hex values.

Extending UIColor
To make the conversion from and to hex values easier, I created an extension for UIColor. You can find the extension in the Extensions group in UIColor.swift.
UIColor.swift
import UIKit
extension UIColor {
static let bitterSweet = UIColor(red:0.99, green:0.47, blue:0.44, alpha:1.0)
}
extension UIColor {
// MARK: - Initialization
convenience init?(hex: String) {
var hexNormalized = hex.trimmingCharacters(in: .whitespacesAndNewlines)
hexNormalized = hexNormalized.replacingOccurrences(of: "#", with: "")
// Helpers
var rgb: UInt32 = 0
var r: CGFloat = 0.0
var g: CGFloat = 0.0
var b: CGFloat = 0.0
var a: CGFloat = 1.0
let length = hexNormalized.characters.count
// Create Scanner
Scanner(string: hexNormalized).scanHexInt32(&rgb)
if length == 6 {
r = CGFloat((rgb & 0xFF0000) >> 16) / 255.0
g = CGFloat((rgb & 0x00FF00) >> 8) / 255.0
b = CGFloat(rgb & 0x0000FF) / 255.0
} else if length == 8 {
r = CGFloat((rgb & 0xFF000000) >> 24) / 255.0
g = CGFloat((rgb & 0x00FF0000) >> 16) / 255.0
b = CGFloat((rgb & 0x0000FF00) >> 8) / 255.0
a = CGFloat(rgb & 0x000000FF) / 255.0
} else {
return nil
}
self.init(red: r, green: g, blue: b, alpha: a)
}
// MARK: - Convenience Methods
var toHex: String? {
// Extract Components
guard let components = cgColor.components, components.count >= 3 else {
return nil
}
// Helpers
let r = Float(components[0])
let g = Float(components[1])
let b = Float(components[2])
var a = Float(1.0)
if components.count >= 4 {
a = Float(components[3])
}
// Create Hex String
let hex = String(format: "%02lX%02lX%02lX%02lX", lroundf(r * 255), lroundf(g * 255), lroundf(b * 255), lroundf(a * 255))
return hex
}
}
Extending Category
We can go one step further and create an extension for the Category class to abstract any interactions with hex values from the Category class. The goal is that we only deal with UIColor instances.
Create a new Swift file in the Core Data > Extensions group and name it Category. Add an import statement for UIKit and create an extension for the Category class.
Category.swift
import UIKit
extension Category {
}
We declare a computed property, color, of type UIColor?. We define a custom getter and setter for the computed property.
Category.swift
import UIKit
extension Category {
var color: UIColor? {
get {
}
set(newColor) {
}
}
}
In the getter, we convert the value of the colorAsHex property to a UIColor instance.
Category.swift
get {
guard let hex = colorAsHex else { return nil }
return UIColor(hex: hex)
}
In the setter, we convert the UIColor instance to a hex value and update the value of the colorAsHex property. This small improvement will keep the code we write clean and focused.
Category.swift
set(newColor) {
if let newColor = newColor {
colorAsHex = newColor.toHex
}
}
Updating the Category View Controller
Next, we need to update the CategoryViewController class. We need to pass the current color of the category to the color view controller. We do this in the prepare(for:sender:) method.
CategoryViewController.swift
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let identifier = segue.identifier else { return }
switch identifier {
case Segue.Color:
...
// Configure Destination
destination.delegate = self
destination.color = category?.color ?? .white
default:
break
}
}
We also need to update the delegate method of the ColorViewControllerDelegate protocol. In this method, we set the color computed property of the category.
CategoryViewController.swift
func controller(_ controller: ColorViewController, didPick color: UIColor) {
// Update Category
category?.color = color
// Update View
updateColorView()
}
Last but not least, we need to update the updateColorView() method of the category view controller. In this method, we set the background color of the color view with the color of the category.
CategoryViewController.swift
private func updateColorView() {
// Configure Color View
colorView.backgroundColor = category?.color
}
I'm sure you agree that the color computed property keeps the implementation focused by removing any logic related to value transformations.
Updating the Notes View Controller
Before we run the application, we need to update the user interface of the notes view controller. I already updated the NoteTableViewCell class. On the left, it contains a narrow subview that displays the category color. This is a subtle hint for the user, showing them which category the note belongs to.

We update the background color of this subview in the configure(_:at:) method, the helper method we created earlier in this series. We safely unwrap the value of the color property and update the backgroundColor property of the category color view. Even though we default to white if the category doesn't have a color, this shouldn't happen in production.
We could also create an extension for the Category class to encapsulate the fallback to the default color if no color is set. That's the approach we took earlier in this series for the Note class.
NotesViewController.swift
func configure(_ cell: NoteTableViewCell, at indexPath: IndexPath) {
...
if let color = note.category?.color {
cell.categoryColorView.backgroundColor = color
} else {
cell.categoryColorView.backgroundColor = .white
}
}
A Crash
Run the application to see the new feature in action. Wait a minute. That doesn't look good. The application crashed. The persistent store coordinator wasn't able to add the persistent store.
If we take a closer look at the error message in the console, we see that the persistent store isn't compatible with the data model.
Console
CoreData: error: -addPersistentStoreWithType:SQLite configuration:(null) URL:file:///var/mobile/Containers/Data/Application/40950C64-3D8E-45AF-9890-CCAF59444996/Documents/Notes.sqlite options:(null) ... returned error Error Domain=NSCocoaErrorDomain Code=134100 "The managed object model version used to open the persistent store is incompatible with the one that was used to create the persistent store." UserInfo={metadata={
NSPersistenceFrameworkVersion = 832;
NSStoreModelVersionHashes = {
Category = <fa37182c b55c9960 577e91ae b9fc0c14 092dcec2 564459a1 19bb513f 45641c4a>;
Note = <b76dea89 b30116c0 c283030a e66c8678 e411956b fee29910 fbbe70be 034a4d56>;
Tag = <b740a6fb 4c426dd1 4ea60b33 5a71b968 da756f6f e3482227 2cb4a849 ebf7dc73>;
};
NSStoreModelVersionHashesVersion = 3;
NSStoreModelVersionIdentifiers = (
""
);
NSStoreType = SQLite;
NSStoreUUID = "A36D0B58-5E20-4F2C-AC20-111EC9F0D0E3";
"_NSAutoVacuumLevel" = 2;
}, reason=The model used to open the store is incompatible with the one used to create the store} with userInfo dictionary {
metadata = {
NSPersistenceFrameworkVersion = 832;
NSStoreModelVersionHashes = {
Category = <fa37182c b55c9960 577e91ae b9fc0c14 092dcec2 564459a1 19bb513f 45641c4a>;
Note = <b76dea89 b30116c0 c283030a e66c8678 e411956b fee29910 fbbe70be 034a4d56>;
Tag = <b740a6fb 4c426dd1 4ea60b33 5a71b968 da756f6f e3482227 2cb4a849 ebf7dc73>;
};
NSStoreModelVersionHashesVersion = 3;
NSStoreModelVersionIdentifiers = (
""
);
NSStoreType = SQLite;
NSStoreUUID = "A36D0B58-5E20-4F2C-AC20-111EC9F0D0E3";
"_NSAutoVacuumLevel" = 2;
};
reason = "The model used to open the store is incompatible with the one used to create the store";
}
The managed object model version used to open the persistent store is incompatible with the one that was used to create the persistent store.
Earlier in this series, we ran into the same issue and I told you we would tackle this problem later in the series. Well ... it's time to talk about migrations.