Chapters

Hide chapters

Core Data by Tutorials

Seventh Edition · iOS 13 · Swift 5.2 · Xcode 11

Before You Begin

Section 0: 14 chapters
Show chapters Hide chapters

2. NSManagedObject Subclasses
Written by Pietro Rea

You got your feet wet with a simple Core Data app in Chapter 1; now it’s time to explore more of what Core Data has to offer!

At the core of this chapter is the subclassing of NSManagedObject to make your own classes for each data entity. This creates a direct one-to-one mapping between entities in the data model editor and classes in your code. This means in some parts of your code, you can work with objects and properties without worrying too much about the Core Data side of things.

Along the way, you’ll learn about all the data types available in Core Data entities, including a few outside the usual string and number types. And with all the data type options available, you’ll also learn about validating data to automatically check values before saving.

Getting started

Head over to the files accompanying this book and open the sample project named BowTies in the starter folder. Like HitList, this project uses Xcode’s Core Data-enabled Single View App template. And like before, this means Xcode generated its own ready-to-use Core Data stack located in AppDelegate.swift.

Open Main.storyboard. Here you’ll find the sample project’s single-page UI:

As you can probably guess, BowTies is a lightweight bow tie management application. You can switch between the different colors of bow ties you own — the app assumes one of each — using the topmost segmented control. Tap “R” for red, “O” for orange and so on.

Tapping on a particular color pulls up an image of the tie and populates several labels on the screen with specific information about the tie. This includes:

  • The name of the bow tie (so you can tell similarly-colored ones apart)
  • The number of times you’ve worn the tie
  • The date you last wore the tie
  • Whether the tie is a favorite of yours

The Wear button on the bottom-left increments the number of times you’ve worn that particular tie and sets the last worn date to today.

Orange is not your color? Not to worry. The Rate button on the bottom-right changes a bow tie’s rating. This particular rating system uses a scale from 0 to 5, allowing for decimal values.

That’s what the application is supposed to do in its final state. Open ViewController.swift to see what it currently does:

import UIKit

class ViewController: UIViewController {

  // MARK: - IBOutlets
  @IBOutlet weak var segmentedControl: UISegmentedControl!
  @IBOutlet weak var imageView: UIImageView!
  @IBOutlet weak var nameLabel: UILabel!
  @IBOutlet weak var ratingLabel: UILabel!
  @IBOutlet weak var timesWornLabel: UILabel!
  @IBOutlet weak var lastWornLabel: UILabel!
  @IBOutlet weak var favoriteLabel: UILabel!
  @IBOutlet weak var wearButton: UIButton!
  @IBOutlet weak var rateButton: UIButton!

  // MARK: - View Life Cycle
  override func viewDidLoad() {
    super.viewDidLoad()
  }

  // MARK: - IBActions
  @IBAction func segmentedControl(
    _ sender: UISegmentedControl) {

  }

  @IBAction func wear(_ sender: UIButton) {

  }

  @IBAction func rate(_ sender: UIButton) {

  }
}

The bad news is in its current state, BowTies doesn’t do anything. The good news is you don’t need to do any Ctrl-dragging!

The segmented control and all the labels on the user interface are already connected to IBOutlets in code. In addition, the segmented control, Wear and Rate button all have corresponding IBActions.

It looks like you have everything you need to get started adding some Core Data — but wait, what are you going to display onscreen? There’s no input method to speak of, so the app must ship with sample data. That’s exactly right. BowTies includes a property list called SampleData.plist containing the information for seven sample ties, one for each color of the rainbow.

Furthermore, the application’s asset catalog Assets.xcassets contains seven images corresponding to the seven bow ties in SampleData.plist.

What you have to do now is take this sample data, store it in Core Data and use it to implement the bow tie management functionality.

Modeling your data

In the previous chapter, you learned one of the first things you have to do when starting a new Core Data project is create your data model.

Open BowTies.xcdatamodeld and click Add Entity on the lower-left to create a new entity. Double-click on the new entity and change its name to BowTie, like so:

In the previous chapter, you created a simple Person entity with a single string attribute to hold the person’s name. Core Data supports several other data types, and you’ll use most of them for the new BowTie entity.

An attribute’s data type determines what kind of data you can store in it and how much space it will occupy on disk. In Core Data, an attribute’s data type begins as Undefined so you’ll have to change it to something else.

If you remember from SampleData.plist, each bow tie has ten associated pieces of information. This means the BowTie entity will end up with at least ten attributes in the model editor.

Select BowTie on the left-hand side and click the plus sign (+) under Attributes. Change the new attribute’s name to name and set its type to String:

Repeat this process seven more times to add the following attributes:

  • A Boolean named isFavorite
  • A Date named lastWorn
  • A Double named rating
  • A String named searchKey
  • An Integer 32 named timesWorn
  • A UUID named id
  • A URI named url

Most of these data types are common in everyday programming. If you haven’t heard of a UUID before, it’s short for universally unique identifier and it’s commonly used to uniquely identify information.

URI stands for uniform resource identifier and it’s used to name and identify different resources like files and web pages. In fact, all URLs are URIs!

When you’re finished, your Attributes section should look similar to the following:

Don’t worry if the order of the attributes is different — all that matters is the attribute names and types are correct.

Note: You may have noticed you have three options for the timesWorn integer attribute: Integer 16, Integer 32 or Integer 64.

16, 32 and 64 refer to the number of bits representing the integer. This is important for two reasons: the number of bits reflects how much space an integer takes up on disk as well as how many values it can represent, also known as its range. Here are the ranges for the three types of integers:

Range for 16-bit integer: -32768 to 32767

Range for 32-bit integer: –2147483648 to 2147483647

Range for 64-bit integer: –9223372036854775808 to 9223372036854775807

How do you choose? The source of your data will dictate the best type of integer. You are assuming your users really like bow ties, so a 32-bit integer should offer enough storage for a lifetime of bow tie wear.

Each bow tie has an associated image. How will you store it in Core Data? Add one more attribute to the BowTie entity, name it photoData and change its data type to Binary Data:

Core Data provides the option of storing arbitrary blobs of binary data directly in your data model. These could be anything from images, to PDF files, to anything that can be serialized into zeroes and ones.

As you can imagine, this convenience can come at a steep cost. Storing a large amount of binary data in the same SQLite database as your other attributes will likely impact your app’s performance. That means a giant binary blob would be loaded into memory each time you access an entity, even if you only need to access its name!

Luckily, Core Data anticipates this problem. With the photoData attribute selected, open the Attributes Inspector and check the Allows External Storage option.

When you enable Allows External Storage, Core Data heuristically decides on a per-value basis if it should save the data directly in the database or store a URI that points to a separate file.

Note: The Allows External Storage option is only available for the binary data attribute type. In addition, if you turn it on, you won’t be able to query Core Data using this attribute.

In summary, besides Strings, Integers, Doubles, Booleans and Dates, Core Data can also save Binary Data, and it can do so efficiently and intelligently.

Storing non-standard data types in Core Data

Still, there are many other types of data you may want to save. For example, what would you do if you had to store an instance of UIColor?

With the options presented so far, you’d have to deconstruct the color into its individual components and save them as integers (e.g., red: 255, green: 101, blue: 155). Then, after fetching these components, you’d have to reconstitute your color at runtime.

Alternatively, you could serialize the UIColor instance to Data and save it as binary data. Then again, you’d also have to “add water” afterward to reconstitute the binary data back to the UIColor object you wanted in the first place.

Once again, Core Data has your back. If you took a close look at SampleData.plist, you probably noticed each bow tie has an associated color. Select the BowTie entity in the model editor and add a new attribute named tintColor of data type Transformable.

You can save any data type to Core Data (even ones you define) using the Transformable type as long as your type conforms to the NSCoding protocol.

UIColor conforms to NSSecureCoding, which inherits from NSCoding, so it can use the transformable type out of the box. If you wanted to save your own custom object, you’d first have to implement the NSCoding protocol.

Note: The NSCoding protocol (not to be confused with Swift’s Codable protocol) is a simple way to archive and unarchive objects that descend from NSObject into data buffers so they can be saved to disk.

Your data model is now complete. The BowTie entity has the ten attributes it needs to store all the information in SampleData.plist.

Managed object subclasses

In the sample project from the last chapter, you used key-value coding to access the attributes on the Person entity. It looked similar to the following:

// Set the name
person.setValue(aName, forKeyPath: "name")

// Get the name
let name = person.value(forKeyPath: "name")

Even though you can do everything directly on NSManagedObject using key-value coding, that doesn’t mean you should!

The biggest problem with key-value coding is you’re accessing data using strings instead of strongly-typed classes. This is often jokingly referred to as writing stringly typed code.

As you probably know from experience, stringly typed code is vulnerable to silly human errors such as mistyping and misspelling. Key-value coding also doesn’t take full advantage of Swift’s type-checking and Xcode’s auto-completion. “There must be another way!” you may be thinking, and you’re right.

The best alternative to key-value coding is to create NSManagedObject subclasses for each entity in your data model. That means there will be a BowTie class with correct types for each property.

Xcode can generate the subclass for you either manually or automatically. Why would you want Xcode to do it for you? It can be a bit of a hassle having to generate these subclass files and have them clutter up your project if you never have to look at them or change them. Since Xcode 8, you can choose, on a per-entity basis, to have Xcode automatically generate and update these files, and store them in the derived data folder for your project.

This setting is in the Codegen field of the Data Model inspector when using the model editor. Because you’re learning about Core Data in this book, you’re not going to use automatic code generation because it helps a lot to be able to easily see the files that have been generated for you.

Make sure you still have BowTies.xcdatamodeld open, select the BowTie entity and open the Data Model inspector. Set the Codegen dropdown to Manual/None, as shown below:

Note: Make sure you change this code generation setting before your first compilation, after you add the BowTie entity to the model.

If you set the code generation setting after your first compilation, you’ll have two versions of the managed object subclass: one in derived data, and a second one in your source code. If this happens, you’ll run into problems when you try to compile again.

Now go to Editor\Create NSManagedObject Subclass…. Select the data model and then the BowTie entity in the next two dialog boxes. Click Create to save the file.

Xcode generated two Swift files for you, one called BowTie+CoreDataClass.swift and a second called BowTie+CoreDataProperties.swift. Open BowTie+CoreDataClass.swift. It should look like this:

import Foundation
import CoreData

@objc(BowTie)
public class BowTie: NSManagedObject {

}

Next, open BowTie+CoreDataProperties.swift. Your generated properties may not be in the same order as shown here, but the file should look similar to the following:

import Foundation
import CoreData

extension BowTie {

  @nonobjc public class func fetchRequest() 
    -> NSFetchRequest<BowTie> {
    
    return NSFetchRequest<BowTie>(entityName: "BowTie")
  }

  @NSManaged public var name: String?
  @NSManaged public var isFavorite: Bool
  @NSManaged public var lastWorn: Date?
  @NSManaged public var rating: Double
  @NSManaged public var searchKey: String?
  @NSManaged public var timesWorn: Int32
  @NSManaged public var id: UUID?
  @NSManaged public var url: URL?
  @NSManaged public var photoData: Data?
  @NSManaged public var tintColor: NSObject?
}

In object-oriented parlance, an object is a set of values along with a set of operations defined on those values. In this case, Xcode separates these two things into two separate files. The values (i.e. the properties that correspond to the BowTie attributes in your data model) are in BowTie+CoreDataProperties.swift, whereas the operations are in the currently empty BowTie+CoreDataClass.swift.

Note: If your BowTie entity changes, you can go to Editor\Create NSManagedObject Subclass… one more time to re-generate BowTie+CoreDataProperties.swift. The second time you do this, you won’t re-generate BowTie+CoreDataClass.swift, so no overwriting any methods you added there. In fact, this is the primary reason why Core Data generates two files, instead of generating one as it used to do in previous versions of Xcode.

Xcode has created a class with a property for each attribute in your data model.

There is a corresponding class in Foundation or in the Swift standard library for every attribute type in the model editor. Here’s the full mapping of attribute types to runtime classes:

  • String maps to String?
  • Integer 16 maps to Int16
  • Integer 32 maps to Int32
  • Integer 64 maps to Int64
  • Float maps to Float
  • Double maps to Double
  • Boolean maps to Bool
  • Decimal maps to NSDecimalNumber?
  • Date maps to Date?
  • URI maps to URL?
  • UUID maps to UUID?
  • Binary data maps to Data?
  • Transformable maps to NSObject?

Note: Similar to @dynamic in Objective-C, the @NSManaged attribute informs the Swift compiler that the backing store and implementation of a property will be provided at runtime instead of compile time.

The normal pattern is for a property to be backed by an instance variable in memory. A property on a managed object is different: It’s backed by the managed object context, so the source of the data is not known at compile time.

Congratulations, you’ve just made your first managed object subclass in Swift!

Compared with key-value coding, this is a much better way of working with Core Data entities and has two main benefits:

  1. Managed object subclasses unleash the syntactic power of Swift properties. By accessing attributes using properties instead of key-value coding, you befriend Xcode and the compiler.

  2. You gain the ability to override existing methods or to add your own. Note there are some NSManagedObject methods you must never override. Check Apple’s documentation of NSManagedObject for a complete list.

To make sure everything is hooked up correctly between the data model and your new managed object subclass, you’ll perform a small test.

Open AppDelegate.swift and replace application(_:didFinishLaunchingWithOptions:) with the following implementation:

func application(_ application: UIApplication,
                 didFinishLaunchingWithOptions
  launchOptions: [UIApplication.LaunchOptionsKey: Any]?)
                 -> Bool {
  
  // Save test bow tie
  let bowtie = NSEntityDescription.insertNewObject(
    forEntityName: "BowTie",
    into: self.persistentContainer.viewContext) as! BowTie
  bowtie.name = "My bow tie"
  bowtie.lastWorn = Date()
  saveContext()
  
  // Retrieve test bow tie
  let request: NSFetchRequest<BowTie> = BowTie.fetchRequest()
  
  if let ties =
    try? self.persistentContainer.viewContext.fetch(request),
    let testName = ties.first?.name,
    let testLastWorn = ties.first?.lastWorn {
    print("Name: \(testName), Worn: \(testLastWorn)")
  } else {
    print("Test failed.")
  }
  
  return true
}

On app launch, this test creates a bow tie and sets its name and lastWorn properties before saving the managed object context. Immediately after that, it fetches all BowTie entities and prints the name and the lastWorn date of the first one to the console; there should only be one at this point. Build and run the application and pay close attention to the console:

Name: My bow tie, Worn: 2019-07-28 03:00:28 +0000

If you’ve been following along carefully, name and lastWorn print to the console as expected. This means you were able to save and fetch a BowTie managed object subclass successfully. With this new knowledge under your belt, it’s time to implement the entire sample app.

Propagating a managed context

Open ViewController.swift and add the following below import UIKit:

import CoreData

Next, add the following below the last IBOutlet property:

// MARK: - Properties
var managedContext: NSManagedObjectContext!

To reiterate, before you can do anything in Core Data, you first have to get an NSManagedObjectContext to work with. Knowing how to propagate a managed object context to different parts of your app is an important aspect of Core Data programming.

Open AppDelegate.swift and replace application(_:didFinishLaunchingWithOptions:), which currently contains the test code, with the following implementation:

func application(_ application: UIApplication,
                  didFinishLaunchingWithOptions
  launchOptions: [UIApplication.LaunchOptionsKey: Any]?)
  -> Bool {
  return true
}

You’ve got seven bow ties dying to enter your Core Data store. Open ViewController.swift and add the following method below rate(_:):

// Insert sample data
  func insertSampleData() {

    let fetch: NSFetchRequest<BowTie> = BowTie.fetchRequest()
    fetch.predicate = NSPredicate(format: "searchKey != nil")

    let count = try! managedContext.count(for: fetch)

    if count > 0 {
      // SampleData.plist data already in Core Data
      return
    }
    let path = Bundle.main.path(forResource: "SampleData",
                                ofType: "plist")
    let dataArray = NSArray(contentsOfFile: path!)!

    for dict in dataArray {
      let entity = NSEntityDescription.entity(
        forEntityName: "BowTie",
        in: managedContext)!
      let bowtie = BowTie(entity: entity,
                          insertInto: managedContext)
      let btDict = dict as! [String: Any]

      bowtie.id = UUID(uuidString: btDict["id"] as! String)
      bowtie.name = btDict["name"] as? String
      bowtie.searchKey = btDict["searchKey"] as? String
      bowtie.rating = btDict["rating"] as! Double
      let colorDict = btDict["tintColor"] as! [String: Any]
      bowtie.tintColor = UIColor.color(dict: colorDict)

      let imageName = btDict["imageName"] as? String
      let image = UIImage(named: imageName!)
      bowtie.photoData = image?.pngData()
      bowtie.lastWorn = btDict["lastWorn"] as? Date

      let timesNumber = btDict["timesWorn"] as! NSNumber
      bowtie.timesWorn = timesNumber.int32Value
      bowtie.isFavorite = btDict["isFavorite"] as! Bool
      bowtie.url = URL(string: btDict["url"] as! String)
    }
    try! managedContext.save()
  }

Xcode will complain about a missing method declaration on UIColor. To fix this, add the following private UIColor extension to the end of the file below the last curly brace.

private extension UIColor {
  
  static func color(dict: [String : Any]) -> UIColor? {
  
    guard let red = dict["red"] as? NSNumber,
      let green = dict["green"] as? NSNumber,
      let blue = dict["blue"] as? NSNumber else {
        return nil
    }
    
    return UIColor(red: CGFloat(truncating: red) / 255.0,
                   green: CGFloat(truncating: green) / 255.0,
                   blue: CGFloat(truncating: blue) / 255.0,
                   alpha: 1)
  }
}

That’s quite a bit of code, but it’s all relatively straightforward. The first method, insertSampleData, checks for any bow ties; you’ll learn how this works later. If none are present, it grabs the bow tie information in SampleData.plist, iterates through each bow tie dictionary and inserts a new BowTie entity into your Core Data store. At the end of this iteration, it saves the managed object context property to commit these changes to disk.

The color(dict:) method you added to UIColor via private extension is also simple. SampleData.plist stores colors in a dictionary containing three keys: red, green and blue. This static method takes in this dictionary and returns a bona fide UIColor.

There are two things here to make special note of:

  1. The way you store images in Core Data. The property list contains a file name for each bow tie, not the file image — the actual images are in the project’s asset catalog. With this file name, you instantiate the UIImage and immediately convert it into Data by means of pngData() before storing it in the imageData property.

  2. The way you store the color. Even though the color is stored in a transformable attribute, it doesn’t require any special treatment before you store it in tintColor. You simply set the property and you’re good to go.

The previous methods insert all the bow tie data you had in SampleData.plist into Core Data. Now you need to access the data from somewhere!

Next, replace viewDidLoad() with the following implementation:

// MARK: - View Life Cycle
override func viewDidLoad() {
  super.viewDidLoad()

  let appDelegate = 
    UIApplication.shared.delegate as? AppDelegate
  managedContext = appDelegate?.persistentContainer.viewContext

  //1
  insertSampleData()

  //2
  let request: NSFetchRequest<BowTie> = BowTie.fetchRequest()
  let firstTitle = segmentedControl.titleForSegment(at: 0)!
  request.predicate = NSPredicate(
    format: "%K = %@",
    argumentArray: [#keyPath(BowTie.searchKey), firstTitle])

  do {
    //3
    let results = try managedContext.fetch(request)

    //4
    populate(bowtie: results.first!)
  } catch let error as NSError {
    print("Could not fetch \(error), \(error.userInfo)")
  }
}

This is where you fetch the bow ties from Core Data and populate the UI.

Step by step, here’s what you’re doing with this code:

  1. You call insertSampleData(), which you implemented earlier. Since viewDidLoad() can be called every time the app is launched, insertSampleData() performs a fetch to make sure it isn’t inserting the sample data into Core Data multiple times.

  2. You create a fetch request for the purpose of fetching the newly inserted BowTie entities. The segmented control has tabs to filter by color, so the predicate adds the condition to find the bow ties matching the selected color. Predicates are both very flexible and very powerful — you’ll read more about them in Chapter 4, “Intermediate Fetching.”

    For now, know this particular predicate is looking for bow ties with their searchKey property set to the segmented control’s first button title: in this case, R.

  3. As always, the managed object context does the heavy lifting for you. It executes the fetch request you crafted moments earlier and returns an array of BowTie objects.

  4. You populate the user interface with the first bow tie in the results array. If there was an error, print the error to the console.

You haven’t defined the populate method yet, so Xcode is throwing a warning. Add the following implementation below insertSampleData():

func populate(bowtie: BowTie) {
  
  guard let imageData = bowtie.photoData as Data?,
    let lastWorn = bowtie.lastWorn as Date?,
    let tintColor = bowtie.tintColor as? UIColor else {
      return
  }
  
  imageView.image = UIImage(data: imageData)
  nameLabel.text = bowtie.name
  ratingLabel.text = "Rating: \(bowtie.rating)/5"
  
  timesWornLabel.text = "# times worn: \(bowtie.timesWorn)"
  
  let dateFormatter = DateFormatter()
  dateFormatter.dateStyle = .short
  dateFormatter.timeStyle = .none
  
  lastWornLabel.text =
    "Last worn: " + dateFormatter.string(from: lastWorn)
  
  favoriteLabel.isHidden = !bowtie.isFavorite
  view.tintColor = tintColor
}

There’s a UI element for most attributes defined in a bow tie. Since Core Data only stores the image as a blob of binary data, it’s your job to reconstitute it back into an image so the view controller’s image view can use it.

Similarly, you can’t use the lastWorn date attribute directly. You first need to create a date formatter to turn the date into a string humans can understand.

Finally, the tintColor transformable attribute that stores your bow tie’s color changes the color of not one, but all the elements on the screen. Simply set the tint color on the view controller’s view and voilà! Everything is now tinted the same color.

Note: Xcode generates some NSManagedObject subclass properties as optional types. That’s why inside the populate method, you unwrap some of the Core Data properties on BowTie using a guard statement at the beginning of the method.

Build and run the app. The red bow tie appears on the screen, like so:

The Wear and Rate buttons do nothing at the moment. Tapping on the different parts of the segmented controls also does nothing. You’ve still got work to do!

First, you need to keep track of the currently selected bow tie so you can reference it from anywhere in your class. Still in ViewController.swift, add the following property below managedContext to do this:

var currentBowTie: BowTie!

Next, replace the do-catch statement in viewDidLoad() with the following to use currentBowTie:

do {
  let results = try managedContext.fetch(request)
  currentBowTie = results.first
  
  populate(bowtie: results.first!)
} catch let error as NSError {
  print("Could not fetch \(error), \(error.userInfo)")
}

Keeping track of the currently selected bow tie is necessary to implement the Wear and Rate buttons since these actions only affect the current bow tie.

Every time the user taps on Wear, the button executes the wear(_:) action method. But wear(_:) is empty at the moment. Replace the wear(_:) implementation with the following:

@IBAction func wear(_ sender: UIButton) {
  
  let times = currentBowTie.timesWorn
  currentBowTie.timesWorn = times + 1
  currentBowTie.lastWorn = Date()
  
  do {
    try managedContext.save()
    populate(bowtie: currentBowTie)    
  } catch let error as NSError {    
    print("Could not fetch \(error), \(error.userInfo)")
  }
}

This method takes the currently selected bow tie and increments its timesWorn attribute by one. Next, you change the lastWorn date to today and save the managed object context to commit these changes to disk. Finally, you populate the user interface to visualize these changes.

Build and run the application and tap Wear as many times as you’d like. It looks like you thoroughly enjoy the timeless elegance of a red bow tie!

Similarly, every time the user taps on Rate, it executes the rate(_:) action method in your code. rate(_:) is currently empty. Replace the implementation of rate(_:) with the following:

@IBAction func rate(_ sender: UIButton) {

  let alert = UIAlertController(title: "New Rating",
                                message: "Rate this bow tie",
                                preferredStyle: .alert)

  alert.addTextField { (textField) in
    textField.keyboardType = .decimalPad
  }

  let cancelAction = UIAlertAction(title: "Cancel",
                                   style: .cancel)

  let saveAction = UIAlertAction(title: "Save",
                                 style: .default) {
    [unowned self] action in

    if let textField = alert.textFields?.first {
      self.update(rating: textField.text)
    }
  }

  alert.addAction(cancelAction)
  alert.addAction(saveAction)
  
  present(alert, animated: true)
}

Tapping on Rate now brings up an alert view controller with a single text field, a cancel button and a save button. Tapping the save button calls update(rating:), which…

Whoops, you haven’t defined that method yet. Appease Xcode by adding the following implementation below populate(bowtie:):

func update(rating: String?) {

  guard let ratingString = rating,
    let rating = Double(ratingString) else {
      return
  }

  do {
    currentBowTie.rating = rating
    try managedContext.save()
    populate(bowtie: currentBowTie)
  } catch let error as NSError {    
    print("Could not save \(error), \(error.userInfo)")
  }
}

You convert the text from the alert view’s text field into a Double and use it to update the current bow ties rating property. Finally, you commit your changes as usual by saving the managed object context and refresh the UI to see your changes in real time.

Try it out. Build and run the app and tap Rate:

Enter any decimal number from 0 to 5 and tap Save. As you’d expect, the rating label updates to the new value you entered. Now tap Rate one more time. Remember the timeless elegance of a red bow tie? Let’s say you like it so much you decide to rate it a 6 out of 5. Tap Save to refresh the user interface:

While you may absolutely love the color red, this is neither the time nor the place for hyperbole. Your app let you save a 6 for a value that’s only supposed to go up to 5. You’ve got invalid data on your hands.

Data validation in Core Data

Your first instinct may be to write client-side validation—something like, “Only save the new rating if the value is greater than 0 and less than 5.” Fortunately, you don’t have to write this code yourself. Core Data supports validation for most attribute types out of the box.

Open BowTies.xcdatamodeld, select the rating attribute and open the data model inspector.

Next to Validation, type 0 for minimum and 5 for maximum. That’s it! No need to write any Swift to reject invalid data.

Note: Normally, you have to version your data model if you want to change it after you’ve shipped your app. You’ll learn more about this in Chapter 6, “Versioning and Migration.”

Attribute validation is one of the few exceptions. If you add it to your app after shipping, you don’t have to version your data model. Lucky you!

But what does this do, exactly?

Validation kicks in immediately after you call save() on your managed object context. The managed object context checks with the model to see if any of the new values conflict with the validation rules you’ve put in place.

If there’s a validation error, the save fails. Remember that NSError in the do-catch block wrapping the save method? Up until now, you’ve had no reason to do anything special if there’s an error other than log it to the console. Validation changes that.

Build and run the app once more. Give the red bowtie a rating of 6 out of 5 and save. A rather cryptic error message will spill out onto your console:

Could not save Error Domain=NSCocoaErrorDomain Code=1610 "rating is too large." UserInfo={NSValidationErrorObject=<BowTie: 0x600002b8ab20> (entity: BowTie; id: 0xcef31f910384f2ad <x-coredata://A64812B6-5D4D-4934-805C-72F6A345EC7B/BowTie/p5>; data: {
    id = "800C3526-E83A-44AC-B718-D36934708921";
    isFavorite = 0;
    lastWorn = "2019-07-28 04:08:02 +0000";
    name = "Red Bow Tie";
    photoData = "{length = 50, bytes = 0x89504e47 0d0a1a0a 0000000d 49484452 ... aece1ce9 00000078 }";
    rating = 6;
    searchKey = R;
    timesWorn = 28;
    tintColor = "UIExtendedSRGBColorSpace 0.937255 0.188235 0.141176 1";
    url = "https://en.wikipedia.org/wiki/Bow_tie";
}), NSLocalizedDescription=rating is too large., NSValidationErrorKey=rating, NSValidationErrorValue=6}, ["NSValidationErrorKey": rating, "NSLocalizedDescription": rating is too large., "NSValidationErrorValue": 6, "NSValidationErrorObject": <BowTie: 0x600002b8ab20> (entity: BowTie; id: 0xcef31f910384f2ad <x-coredata://A64812B6-5D4D-4934-805C-72F6A345EC7B/BowTie/p5>; data: {
    id = "800C3526-E83A-44AC-B718-D36934708921";
    isFavorite = 0;
    lastWorn = "2019-07-28 04:08:02 +0000";
    name = "Red Bow Tie";
    photoData = "{length = 50, bytes = 0x89504e47 0d0a1a0a 0000000d 49484452 ... aece1ce9 00000078 }";
    rating = 6;
    searchKey = R;
    timesWorn = 28;
    tintColor = "UIExtendedSRGBColorSpace 0.937255 0.188235 0.141176 1";
    url = "https://en.wikipedia.org/wiki/Bow_tie";
})]

The userInfo dictionary that comes with the error contains all kinds of useful information about why Core Data aborted your save operation. It even has a localized error message you can show your users, under the key NSLocalizedDescription: rating is too large.

What you do with this error, however, is entirely up to you. Open ViewController.swift and replace update(rating:) with the following to handle the error appropriately:

func update(rating: String?) {

  guard let ratingString = rating,
    let rating = Double(ratingString) else {
      return
  }

  do {

    currentBowTie.rating = rating
    try managedContext.save()
    populate(bowtie: currentBowTie)

  } catch let error as NSError {

    if error.domain == NSCocoaErrorDomain &&
      (error.code == NSValidationNumberTooLargeError ||
        error.code == NSValidationNumberTooSmallError) {
      rate(rateButton)
    } else {
      print("Could not save \(error), \(error.userInfo)")
    }
  }
}

If there’s an error that occurred because the new rating was either too large or too small, then you present the alert view again.

Otherwise, you populate the user interface with the new rating as before.

But wait… Where did NSValidationNumberTooLargeError and NSValidationNumberTooSmallError come from? Go back to the previous console reading and look closely at the first line:

Could not save Error Domain=NSCocoaErrorDomain Code=1610 "rating is too large."

NSValidationNumberTooLargeError is an error code that maps to the integer 1610.

For a full list of Core Data errors and code definitions, you can consult CoreDataErrors.h in Xcode by Control-Cmd-clicking on NSValidationNumberTooLargeError.

Note: When an NSError is involved, it’s standard practice to check the domain and code for the error to determine what went wrong. You can read more about this in Apple’s Error Handling Programming Guide: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ErrorHandlingCocoa/CreateCustomizeNSError/CreateCustomizeNSError.html

Build and run the app. Verify the new validation rules work properly by once again showing the red tie some love.

If you enter any value above 5 and try to save, the app rejects your rating and asks you to try again with a new alert view. Success!

Tying everything up

The Wear and Rate buttons are working properly, but the app can only display one tie. Tapping the different values on the segmented control is supposed to switch ties. You’ll finish up this sample project by implementing that feature.

Every time the user taps the segmented control, it executes the segmentedControl(_:) action method in your code. Replace the implementation of segmentedControl(_:) with the following:

@IBAction func segmentedControl(_ sender: UISegmentedControl) {
  guard let selectedValue = sender.titleForSegment(
    at: sender.selectedSegmentIndex) else {
      return
  }

  let request: NSFetchRequest<BowTie> = BowTie.fetchRequest()
  request.predicate = NSPredicate(
    format: "%K = %@",
    argumentArray: [#keyPath(BowTie.searchKey), selectedValue])

  do {
    let results =  try managedContext.fetch(request)
    currentBowTie =  results.first
    populate(bowtie: currentBowTie)

  } catch let error as NSError {
    print("Could not fetch \(error), \(error.userInfo)")
  }
}

The title of each segment in the segmented control corresponds to a particular tie’s searchKey attribute. Grab the title of the currently selected segment and fetch the appropriate bow tie using a well-crafted NSPredicate.

Then, use the first bow tie in the array of results (there should only be one per searchKey) to populate the user interface.

Build and run the app. Tap different letters on the segmented control for a psychedelic treat.

You did it! With this bow tie app under your belt, you’re well on your way to becoming a Core Data master.

Key points

  • Core Data supports different attribute data types, which determines the kind of data you can store in your entities and how much space they will occupy on disk. Some common attribute data types are String, Date, and Double.
  • The Binary Data attribute data type gives you the option of storing arbitrary amounts of binary data in your data model.
  • The Transformable attribute data type lets you store any object that conforms to NSCoding in your data model.
  • Using an NSManagedObject subclass is a better way to work with a Core Data entity. You can either generate the subclass manually or let Xcode do it automatically.
  • You can refine the set entities fetched by NSFetchRequest using an NSPredicate.
  • You can set validation rules (e.g. maximum value and minimum value) to most attribute data types directly in the data model editor. The managed object context will throw an error if you try to save invalid data.
Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2026 Kodeco Inc.