I discussed creating and reading data with Core Data recently. This post looks at the rest of CRUD functions: updating and deleting. The examples in this post are taken from the demonstration project I used in my London Swift talk.
A bit of background: My SavedColoursGrid class contains a UICollectionView that displays the user's saved colours. These are instances of NamedColour, a value object that contains a name, a color and, as we'll see, a reference to its related data transfer object.
Updating
When a user saves their colour in my application or I load a saved colour at application startup, I add a reference to the related NSManagedObject to the value object itself. These methods are taken from NamedColorEntity:
classfunc createInstanceFromEntity(entity: NamedColorEntity) -> NamedColor!
{
let name = entity.colorName
let color = UIColor.colorFromNSNumbers(redComponent: entity.redComponent, greenComponent: entity.greenComponent, blueComponent: entity.blueComponent)
var namedColor = NamedColor(name: name, color: color)
namedColor.entityRef = entity
return namedColor
}
classfunc createInManagedObjectContext(moc: NSManagedObjectContext, namedColor: NamedColor) -> NamedColorEntity
{
let newEntity = NSEntityDescription.insertNewObjectForEntityForName("NamedColorEntity", inManagedObjectContext: moc) asNamedColorEntity
let colorComponents = namedColor.color.getRGB()
newEntity.redComponent = colorComponents.redComponent
newEntity.greenComponent = colorComponents.greenComponent
newEntity.blueComponent = colorComponents.blueComponent
newEntity.colorName = namedColor.name
namedColor.entityRef = newEntity
return newEntity
}
NamedColorItemRenderer, the item renderer inside the UICollectionView that displays the user's saved colours, contains a text input field for editing. Since the renderer acts a the text input's UITextFieldDelegate, when the user either presses the "return" key or dismisses the keyboard I can react to the text changing and update the saved colour's name property:
func textFieldShouldReturn(textField: UITextField) -> Bool
{
textField.endEditing(true)
returntrue
}
func textFieldDidEndEditing(textField: UITextField)
{
ifnamedColor != nil
{
namedColor!.name = textInput.text
}
}
Then, maybe controversially, I allow the NamedColor value object itself to update its reference to the related data transfer object and save its updated name using Core Data:
var name : String
{
didSet
{
iflet _entityRef = entityRef
{
_entityRef.colorName = name
let appDelegate = UIApplication.sharedApplication().delegateasAppDelegate
appDelegate.saveContext()
}
}
}
I'd suggest this pattern follows the Domain Driven Data pattern. In a small application like mine, I think it's entirely appropriate. However if you're creating a larger application, you may want to marshall all Core Data related work through a single delegate to manage persistence through a single class.
Deleting
My SavedColoursGrid class contains acts as UICollectionViewDelegate for its UICollectionView and with a few of functions, I'm able to display a little 'cut' context menu when the user tap-holds on any of the items:
func collectionView(collectionView: UICollectionView, shouldShowMenuForItemAtIndexPath indexPath: NSIndexPath) -> Bool
{
returntrue
}
func collectionView(collectionView: UICollectionView, canPerformAction action: Selector, forItemAtIndexPath indexPath: NSIndexPath, withSender sender: AnyObject!) -> Bool
{
return action.description == "cut:"
}
When the user selects 'cut', the performAction version of the delegate method collectionView() is invoked where I use an array filter closure to remove the current item from the data:
func collectionView(collectionView: UICollectionView, performAction action: Selector, forItemAtIndexPath indexPath: NSIndexPath, withSender sender: AnyObject!)
{
if action.description == "cut:"
{
colors = colors.filter({$0 != self.colors[indexPath.item]})
sendActionsForControlEvents(UIControlEvents.SavedColorDeleted)
}
}
The view controller, reacting to the SavedColorDeleted control event, compares its own version of savedColors with the newly updated savedColors of the SavedColorsGrid to figure out which item the user wants to delete. Using the context's deleteObject, it's able to remove that item from Core Data:
func savedColorsGridDeleteHandler()
{
for color insavedColors
{
iffind(savedColorsGrid.colors, color) == nil
{
iflet _entityRef = color.entityRef
{
managedObjectContext.deleteObject(_entityRef)
}
}
}
appDelegate.saveContext()
savedColors = savedColorsGrid.colors
}
And the circle is complete: with Core Data I'm now able to create, read, update and delete items.
The source code for this project is all available at my GitHub repository here.