Core Data
can fit organically into the iOS controls. Therefore, I will use for this purpose such an interface, using which the interaction of controls and Core Data
will look simpler and clearer. It is obvious that in the real application of the interface part it will be necessary to devote much more time.AppDelegate.swift
initial view to the application delegate module ( AppDelegate.swift
), in which we experimented in the last part of the article. // AppDelegate.swift // core-data-habrahabr-swift import UIKit import CoreData @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { return true } func applicationWillTerminate(application: UIApplication) { CoreDataManager.instance.saveContext() } }
Editor\Embed In\Navigation Controller
menu)Customers
Action Segue\Show
)UITableViewController
as the parent class and specify the name of our class - CustomersTableViewController
Identity Inspector\Custom Class\Class
).Attributes Inspector\Table View\Prototype Cells
). // CustomersTableViewController.swift // core-data-habrahabr-swift import UIKit import CoreData class CustomersTableViewController: UITableViewController { var fetchedResultsController:NSFetchedResultsController = { let fetchRequest = NSFetchRequest(entityName: "Customer") let sortDescriptor = NSSortDescriptor(key: "name", ascending: true) fetchRequest.sortDescriptors = [sortDescriptor] let fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: CoreDataManager.instance.managedObjectContext, sectionNameKeyPath: nil, cacheName: nil) return fetchedResultsController }() override func viewDidLoad() { super.viewDidLoad() do { try fetchedResultsController.performFetch() } catch { print(error) } } // MARK: - Table View Data Source override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { if let sections = fetchedResultsController.sections { return sections[section].numberOfObjects } else { return 0 } } override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { let customer = fetchedResultsController.objectAtIndexPath(indexPath) as! Customer let cell = UITableViewCell() cell.textLabel?.text = customer.name return cell } }
NSFetchedResultsController
. As you can see, it is created based on NSFetchRequest
(I created NSFetchRequest based on the “Customer” entity and set the sort by Customer name). Then we create the NSFetchedResultsController
itself, passing in its NSFetchRequest
constructor and the managed context we need, we will not use additional constructor parameters (sectionNameKeyPath, cacheName) here.func viewDidLoad()
), we run fetchedResultsController : try fetchedResultsController.performFetch()
«Customers»
menu, we will see all of our customers who were added in the last part of the article. It wasn't too hard, was it? import CoreData import Foundation class CoreDataManager { // Singleton static let instance = CoreDataManager() // Entity for Name func entityForName(entityName: String) -> NSEntityDescription { return NSEntityDescription.entityForName(entityName, inManagedObjectContext: self.managedObjectContext)! } // Fetched Results Controller for Entity Name func fetchedResultsController(entityName: String, keyForSort: String) -> NSFetchedResultsController { let fetchRequest = NSFetchRequest(entityName: entityName) let sortDescriptor = NSSortDescriptor(key: keyForSort, ascending: true) fetchRequest.sortDescriptors = [sortDescriptor] let fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: CoreDataManager.instance.managedObjectContext, sectionNameKeyPath: nil, cacheName: nil) return fetchedResultsController } // MARK: - Core Data stack // ...
var fetchedResultsController = CoreDataManager.instance.fetchedResultsController("Customer", keyForSort: "name")
«Customer»
) and connect it to our Table View Controller .Present Modally
.customersToCustomer
.UIViewController
, the class name is CustomerViewController
.name
, info ), and when the object is saved, on the contrary, the contents of the interface elements will be transferred to the fields of the saved object. // CustomerViewController.swift // core-data-habrahabr-swift import UIKit class CustomerViewController: UIViewController { var customer: Customer? @IBAction func cancel(sender: AnyObject) { dismissViewControllerAnimated(true, completion: nil) } @IBAction func save(sender: AnyObject) { if saveCustomer() { dismissViewControllerAnimated(true, completion: nil) } } @IBOutlet weak var nameTextField: UITextField! @IBOutlet weak var infoTextField: UITextField! override func viewDidLoad() { super.viewDidLoad() // Reading object if let customer = customer { nameTextField.text = customer.name infoTextField.text = customer.info } } func saveCustomer() -> Bool { // Validation of required fields if nameTextField.text!.isEmpty { let alert = UIAlertController(title: "Validation error", message: "Input the name of the Customer!", preferredStyle: .Alert) alert.addAction(UIAlertAction(title: "OK", style: .Cancel, handler: nil)) self.presentViewController(alert, animated: true, completion: nil) return false } // Creating object if customer == nil { customer = Customer() } // Saving object if let customer = customer { customer.name = nameTextField.text customer.info = infoTextField.text CoreDataManager.instance.saveContext() } return true } }
Navigation Item + Bar Button Item
, similar to the Customer card). And create an Action for this button named AddCustomer
. @IBAction func AddCustomer(sender: AnyObject) { performSegueWithIdentifier("customersToCustomer", sender: nil) }
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { let customer = fetchedResultsController.objectAtIndexPath(indexPath) as? Customer performSegueWithIdentifier("customersToCustomer", sender: customer) } override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { if segue.identifier == "customersToCustomer" { let controller = segue.destinationViewController as! CustomerViewController controller.customer = sender as? Customer } }
customer
our “card” customer variable so that when opening it we can read all object data. class CustomersTableViewController: UITableViewController, NSFetchedResultsControllerDelegate {
override func viewDidLoad() { super.viewDidLoad() fetchedResultsController.delegate = self do { try fetchedResultsController.performFetch() } catch { print(error) } }
// MARK: - Fetched Results Controller Delegate func controllerWillChangeContent(controller: NSFetchedResultsController) { tableView.beginUpdates() } func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) { switch type { case .Insert: if let indexPath = newIndexPath { tableView.insertRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic) } case .Update: if let indexPath = indexPath { let customer = fetchedResultsController.objectAtIndexPath(indexPath) as! Customer let cell = tableView.cellForRowAtIndexPath(indexPath) cell!.textLabel?.text = customer.name } case .Move: if let indexPath = indexPath { tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic) } if let newIndexPath = newIndexPath { tableView.insertRowsAtIndexPaths([newIndexPath], withRowAnimation: .Automatic) } case .Delete: if let indexPath = indexPath { tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic) } } } func controllerDidChangeContent(controller: NSFetchedResultsController) { tableView.endUpdates() }
controllerWillChangeContent
and controllerDidChangeContent
, which, respectively, inform about the beginning and end of the data change. Using these functions, we inform our Table View
that now we will change something in the data that it displays (this is necessary for its correct operation). override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) { if editingStyle == .Delete { let managedObject = fetchedResultsController.objectAtIndexPath(indexPath) as! NSManagedObject CoreDataManager.instance.managedObjectContext.deleteObject(managedObject) CoreDataManager.instance.saveContext() } }
NSManagedObject
. // CustomersTableViewController.swift // core-data-habrahabr-swift import UIKit import CoreData class CustomersTableViewController: UITableViewController, NSFetchedResultsControllerDelegate { var fetchedResultsController = CoreDataManager.instance.fetchedResultsController("Customer", keyForSort: "name") override func viewDidLoad() { super.viewDidLoad() fetchedResultsController.delegate = self do { try fetchedResultsController.performFetch() } catch { print(error) } } @IBAction func AddCustomer(sender: AnyObject) { performSegueWithIdentifier("customersToCustomer", sender: nil) } // MARK: - Table View Data Source override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { if let sections = fetchedResultsController.sections { return sections[section].numberOfObjects } else { return 0 } } override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { let customer = fetchedResultsController.objectAtIndexPath(indexPath) as! Customer let cell = UITableViewCell() cell.textLabel?.text = customer.name return cell } // MARK: - Table View Delegate override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) { if editingStyle == .Delete { let managedObject = fetchedResultsController.objectAtIndexPath(indexPath) as! NSManagedObject CoreDataManager.instance.managedObjectContext.deleteObject(managedObject) CoreDataManager.instance.saveContext() } } override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { let customer = fetchedResultsController.objectAtIndexPath(indexPath) as? Customer performSegueWithIdentifier("customersToCustomer", sender: customer) } override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { if segue.identifier == "customersToCustomer" { let controller = segue.destinationViewController as! CustomerViewController controller.customer = sender as? Customer } } // MARK: - Fetched Results Controller Delegate func controllerWillChangeContent(controller: NSFetchedResultsController) { tableView.beginUpdates() } func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) { switch type { case .Insert: if let indexPath = newIndexPath { tableView.insertRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic) } case .Update: if let indexPath = indexPath { let customer = fetchedResultsController.objectAtIndexPath(indexPath) as! Customer let cell = tableView.cellForRowAtIndexPath(indexPath) cell!.textLabel?.text = customer.name } case .Move: if let indexPath = indexPath { tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic) } if let newIndexPath = newIndexPath { tableView.insertRowsAtIndexPaths([newIndexPath], withRowAnimation: .Automatic) } case .Delete: if let indexPath = indexPath { tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic) } } } func controllerDidChangeContent(controller: NSFetchedResultsController) { tableView.endUpdates() } }
«Services»
buttonServicesTableViewController
for it (based on UITableViewController
)import
) CoreData
, add fetchedResultsController
(based on the Service
entity) and launch the controller when the controller loadsServiceViewController
(based on UIViewController
) for this controllerSave
and Cancel
) and two Outlet ( name
and info
fields)service
variable, we register the procedures for loading and saving the object, we don’t forget about checking the data before writing)ServicesTableViewController
and ServiceViewController
named servicesToService
( Segue \ Present Modally
)ServicesTableViewController
and add the Add button to add a new service ( Navigation Item \ Bar Button Item
) and create an Action for it called AddService
NSFetchedResultsControllerDelegate
protocol NSFetchedResultsControllerDelegate
and declare the current class as a delegate // ServicesTableViewController.swift // core-data-habrahabr-swift import UIKit import CoreData class ServicesTableViewController: UITableViewController, NSFetchedResultsControllerDelegate { var fetchedResultsController = CoreDataManager.instance.fetchedResultsController("Service", keyForSort: "name") @IBAction func AddService(sender: AnyObject) { performSegueWithIdentifier("servicesToService", sender: nil) } override func viewDidLoad() { super.viewDidLoad() fetchedResultsController.delegate = self do { try fetchedResultsController.performFetch() } catch { print(error) } } // MARK: - Table View Data Source override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { if let sections = fetchedResultsController.sections { return sections[section].numberOfObjects } else { return 0 } } override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { let service = fetchedResultsController.objectAtIndexPath(indexPath) as! Service let cell = UITableViewCell() cell.textLabel?.text = service.name return cell } // MARK: - Table View Delegate override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) { if editingStyle == .Delete { let managedObject = fetchedResultsController.objectAtIndexPath(indexPath) as! NSManagedObject CoreDataManager.instance.managedObjectContext.deleteObject(managedObject) CoreDataManager.instance.saveContext() } } override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { let service = fetchedResultsController.objectAtIndexPath(indexPath) as? Service performSegueWithIdentifier("servicesToService", sender: service) } override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { if segue.identifier == "servicesToService" { let controller = segue.destinationViewController as! ServiceViewController controller.service = sender as? Service } } // MARK: - Fetched Results Controller Delegate func controllerWillChangeContent(controller: NSFetchedResultsController) { tableView.beginUpdates() } func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) { switch type { case .Insert: if let indexPath = newIndexPath { tableView.insertRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic) } case .Update: if let indexPath = indexPath { let service = fetchedResultsController.objectAtIndexPath(indexPath) as! Service let cell = tableView.cellForRowAtIndexPath(indexPath) cell!.textLabel?.text = service.name } case .Move: if let indexPath = indexPath { tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic) } if let newIndexPath = newIndexPath { tableView.insertRowsAtIndexPaths([newIndexPath], withRowAnimation: .Automatic) } case .Delete: if let indexPath = indexPath { tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic) } } } func controllerDidChangeContent(controller: NSFetchedResultsController) { tableView.endUpdates() } }
// ServiceViewController.swift // core-data-habrahabr-swift import UIKit class ServiceViewController: UIViewController { @IBOutlet weak var nameTextField: UITextField! @IBOutlet weak var infoTextField: UITextField! @IBAction func cancel(sender: AnyObject) { dismissViewControllerAnimated(true, completion: nil) } @IBAction func save(sender: AnyObject) { if saveService() { dismissViewControllerAnimated(true, completion: nil) } } var service: Service? override func viewDidLoad() { super.viewDidLoad() // Reading object if let service = service { nameTextField.text = service.name infoTextField.text = service.info } } func saveService() -> Bool { // Validation of required fields if nameTextField.text!.isEmpty { let alert = UIAlertController(title: "Validation error", message: "Input the name of the Service!", preferredStyle: .Alert) alert.addAction(UIAlertAction(title: "OK", style: .Cancel, handler: nil)) self.presentViewController(alert, animated: true, completion: nil) return false } // Creating object if service == nil { service = Service() } // Saving object if let service = service { service.name = nameTextField.text service.info = infoTextField.text CoreDataManager.instance.saveContext() } return true } }
fetchedResultsController
and implement protocols: // OrdersTableViewController.swift // core-data-habrahabr-swift import UIKit import CoreData class OrdersTableViewController: UITableViewController, NSFetchedResultsControllerDelegate { var fetchedResultsController = CoreDataManager.instance.fetchedResultsController("Order", keyForSort: "date") @IBAction func AddOrder(sender: AnyObject) { performSegueWithIdentifier("ordersToOrder", sender: nil) } override func viewDidLoad() { super.viewDidLoad() fetchedResultsController.delegate = self do { try fetchedResultsController.performFetch() } catch { print(error) } } // MARK: - Table View Data Source override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { if let sections = fetchedResultsController.sections { return sections[section].numberOfObjects } else { return 0 } } override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { let cell = UITableViewCell() let order = fetchedResultsController.objectAtIndexPath(indexPath) as! Order configCell(cell, order: order) return cell } func configCell(cell: UITableViewCell, order: Order) { let formatter = NSDateFormatter() formatter.dateFormat = "MMM d, yyyy" let nameOfCustomer = (order.customer == nil) ? "-- Unknown --" : (order.customer!.name!) cell.textLabel?.text = formatter.stringFromDate(order.date) + "\t" + nameOfCustomer } // MARK: - Table View Delegate override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) { if editingStyle == .Delete { let managedObject = fetchedResultsController.objectAtIndexPath(indexPath) as! NSManagedObject CoreDataManager.instance.managedObjectContext.deleteObject(managedObject) CoreDataManager.instance.saveContext() } } override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { let order = fetchedResultsController.objectAtIndexPath(indexPath) as? Order performSegueWithIdentifier("ordersToOrder", sender: order) } override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { if segue.identifier == "ordersToOrder" { let controller = segue.destinationViewController as! OrderViewController controller.order = sender as? Order } } // MARK: - Fetched Results Controller Delegate func controllerWillChangeContent(controller: NSFetchedResultsController) { tableView.beginUpdates() } func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) { switch type { case .Insert: if let indexPath = newIndexPath { tableView.insertRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic) } case .Update: if let indexPath = indexPath { let order = fetchedResultsController.objectAtIndexPath(indexPath) as! Order let cell = tableView.cellForRowAtIndexPath(indexPath) configCell(cell!, order: order) } case .Move: if let indexPath = indexPath { tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic) } if let newIndexPath = newIndexPath { tableView.insertRowsAtIndexPaths([newIndexPath], withRowAnimation: .Automatic) } case .Delete: if let indexPath = indexPath { tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic) } } } func controllerDidChangeContent(controller: NSFetchedResultsController) { tableView.endUpdates() } }
fetchedResultsController
field for sorting, we specify as “ date ”, that is, the documents will be sorted by their dateconfigCell
@IBAction func choiceCustomer(sender: AnyObject) { performSegueWithIdentifier("orderToCustomers", sender: nil) }
CustomersTableViewController.swift
). First you need to add a closure variable: // CustomersTableViewController.swift // core-data-habrahabr-swift import UIKit import CoreData class CustomersTableViewController: UITableViewController, NSFetchedResultsControllerDelegate { typealias Select = (Customer?) -> () var didSelect: Select?
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { let customer = fetchedResultsController.objectAtIndexPath(indexPath) as? Customer if let dSelect = self.didSelect { dSelect(customer) dismissViewControllerAnimated(true, completion: nil) } else { performSegueWithIdentifier("customersToCustomer", sender: customer) } }
override func viewDidLoad() { super.viewDidLoad() // Creating object if order == nil { order = Order() order?.date = NSDate() } if let order = order { dataPicker.date = order.date switchMade.on = order.made switchPaid.on = order.paid textFieldCustomer.text = order.customer?.name } }
NSDate()
returns the current date / time). And the data recording procedure: func saveOrder() { if let order = order { order.date = dataPicker.date order.made = switchMade.on order.paid = switchPaid.on CoreDataManager.instance.saveContext() } }
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { if segue.identifier == "orderToCustomers" { let viewController = segue.destinationViewController as! CustomersTableViewController viewController.didSelect = { [unowned self] (customer) in if let customer = customer { self.order?.customer = customer self.textFieldCustomer.text = customer.name! } } } }
fetchedResultsController
and implement protocols NSFetchedResultsControllerDelegate
, UITableViewDataSource
and UITableViewDelegate
.fetchedResultsController
, created in the same way as the previous one - we really get all the rows of the table part, but these will be the rows of all documents, and we need only the lines of the current document, the one with which the user works.fetchRequest
. This is done through the mechanism of predicates ( NSPredicate
). We will talk about it a little more at the end of the article, but for now let's just add for our document ( Order.swift
) a class function that will return the table part of the document as NSFetchedResultsController
. class func getRowsOfOrder(order: Order) -> NSFetchedResultsController { let fetchRequest = NSFetchRequest(entityName: "RowOfOrder") let sortDescriptor = NSSortDescriptor(key: "service.name", ascending: true) fetchRequest.sortDescriptors = [sortDescriptor] let predicate = NSPredicate(format: "%K == %@", "order", order) fetchRequest.predicate = predicate let fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: CoreDataManager.instance.managedObjectContext, sectionNameKeyPath: nil, cacheName: nil) return fetchedResultsController }
let sortDescriptor = NSSortDescriptor(key: "service.name", ascending: true)
OrderViewController.swift
, we need to declare a variable that will contain the tabular part and initialize it after initializing the document itself when loading the View Controller . // OrderViewController.swift // core-data-habrahabr-swift import UIKit import CoreData class OrderViewController: UIViewController { var order: Order? var table: NSFetchedResultsController? //… override func viewDidLoad() { super.viewDidLoad() // Creating object if order == nil { order = Order() order?.date = NSDate() } if let order = order { dataPicker.date = order.date switchMade.on = order.made switchPaid.on = order.paid textFieldCustomer.text = order.customer?.name table = Order.getRowsOfOrder(order) table!.delegate = self do { try table!.performFetch() } catch { print(error) } } }
RowOfOrderViewController
. Add the necessary navigation and control elements, Outlet and Action , implement the procedures for reading and writing the object. Also for the amount input field, set the numeric keypad ( Keyboard Type = Number Pad
).RowOfOrder
), which we will immediately create and set in it a link to our document. @IBAction func AddRowOfOrder(sender: AnyObject) { if let order = order { let newRowOfOrder = RowOfOrder() newRowOfOrder.order = order performSegueWithIdentifier("orderToRowOfOrder", sender: newRowOfOrder) } }
// OrderViewController.swift // core-data-habrahabr-swift import UIKit import CoreData class OrderViewController: UIViewController, NSFetchedResultsControllerDelegate, UITableViewDataSource, UITableViewDelegate { var order: Order? var table: NSFetchedResultsController? @IBOutlet weak var dataPicker: UIDatePicker! @IBOutlet weak var textFieldCustomer: UITextField! @IBOutlet weak var tableView: UITableView! @IBAction func save(sender: AnyObject) { saveOrder() dismissViewControllerAnimated(true, completion: nil) } @IBAction func cancel(sender: AnyObject) { dismissViewControllerAnimated(true, completion: nil) } @IBAction func choiceCustomer(sender: AnyObject) { performSegueWithIdentifier("orderToCustomers", sender: nil) } @IBAction func AddRowOfOrder(sender: AnyObject) { if let order = order { let newRowOfOrder = RowOfOrder() newRowOfOrder.order = order performSegueWithIdentifier("orderToRowOfOrder", sender: newRowOfOrder) } } override func viewDidLoad() { super.viewDidLoad() tableView.dataSource = self tableView.delegate = self // Creating object if order == nil { order = Order() order!.date = NSDate() } if let order = order { dataPicker.date = order.date switchMade.on = order.made switchPaid.on = order.paid textFieldCustomer.text = order.customer?.name table = Order.getRowsOfOrder(order) table!.delegate = self do { try table!.performFetch() } catch { print(error) } } } func saveOrder() { if let order = order { order.date = dataPicker.date order.made = switchMade.on order.paid = switchPaid.on CoreDataManager.instance.saveContext() } } override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { switch segue.identifier! { case "orderToCustomers": let viewController = segue.destinationViewController as! CustomersTableViewController viewController.didSelect = { [unowned self] (customer) in if let customer = customer { self.order?.customer = customer self.textFieldCustomer.text = customer.name! } } case "orderToRowOfOrder": let controller = segue.destinationViewController as! RowOfOrderViewController controller.rowOfOrder = sender as? RowOfOrder default: break } } // MARK: - Table View Data Source func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { if let sections = table?.sections { return sections[section].numberOfObjects } else { return 0 } } func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { let rowOfOrder = table?.objectAtIndexPath(indexPath) as! RowOfOrder let cell = UITableViewCell() let nameOfService = (rowOfOrder.service == nil) ? "-- Unknown --" : (rowOfOrder.service!.name!) cell.textLabel?.text = nameOfService + " - " + String(rowOfOrder.sum) return cell } // MARK: - Table View Delegate func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) { if editingStyle == .Delete { let managedObject = table?.objectAtIndexPath(indexPath) as! NSManagedObject CoreDataManager.instance.managedObjectContext.deleteObject(managedObject) CoreDataManager.instance.saveContext() } } func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { let rowOfOrder = table?.objectAtIndexPath(indexPath) as! RowOfOrder performSegueWithIdentifier("orderToRowOfOrder", sender: rowOfOrder) } // MARK: - Fetched Results Controller Delegate func controllerWillChangeContent(controller: NSFetchedResultsController) { tableView.beginUpdates() } func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) { switch type { case .Insert: if let indexPath = newIndexPath { tableView.insertRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic) } case .Update: if let indexPath = indexPath { let rowOfOrder = table?.objectAtIndexPath(indexPath) as! RowOfOrder let cell = tableView.cellForRowAtIndexPath(indexPath)! let nameOfService = (rowOfOrder.service == nil) ? "-- Unknown --" : (rowOfOrder.service!.name!) cell.textLabel?.text = nameOfService + " - " + String(rowOfOrder.sum) } case .Move: if let indexPath = indexPath { tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic) } if let newIndexPath = newIndexPath { tableView.insertRowsAtIndexPaths([newIndexPath], withRowAnimation: .Automatic) } case .Delete: if let indexPath = indexPath { tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic) } } } func controllerDidChangeContent(controller: NSFetchedResultsController) { tableView.endUpdates() } }
rowOfOrderToServices
that connects the View Controller document lines and a Table View Controller with a list of Services. We need to modify the Table View Controller a bit so that we can use the closure. First, add a closure variable: // ServicesTableViewController.swift // core-data-habrahabr-swift import UIKit import CoreData class ServicesTableViewController: UITableViewController, NSFetchedResultsControllerDelegate { typealias Select = (Service?) -> () var didSelect: Select? // …
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { let service = fetchedResultsController.objectAtIndexPath(indexPath) as? Service if let dSelect = self.didSelect { dSelect(service) dismissViewControllerAnimated(true, completion: nil) } else { performSegueWithIdentifier("servicesToService", sender: service) } }
RowOfOrderViewController
and implement the closure. Here everything is based on the same principle as when choosing a customer. @IBAction func choiceService(sender: AnyObject) { performSegueWithIdentifier("rowOfOrderToServices", sender: nil) } override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { if segue.identifier == "rowOfOrderToServices" { let controller = segue.destinationViewController as! ServicesTableViewController controller.didSelect = {[unowned self] (service) in if let service = service { self.rowOfOrder!.service = service self.textFieldService.text = service.name } } } }
// RowOfOrderViewController.swift // core-data-habrahabr-swift import UIKit class RowOfOrderViewController: UIViewController { var rowOfOrder: RowOfOrder? @IBAction func cancel(sender: AnyObject) { dismissViewControllerAnimated(true, completion: nil) } @IBAction func save(sender: AnyObject) { saveRow() dismissViewControllerAnimated(true, completion: nil) } @IBAction func choiceService(sender: AnyObject) { performSegueWithIdentifier("rowOfOrderToServices", sender: nil) } override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { if segue.identifier == "rowOfOrderToServices" { let controller = segue.destinationViewController as! ServicesTableViewController controller.didSelect = {[unowned self] (service) in if let service = service { self.rowOfOrder!.service = service self.textFieldService.text = service.name } } } } @IBOutlet weak var textFieldService: UITextField! @IBOutlet weak var textFieldSum: UITextField! override func viewDidLoad() { super.viewDidLoad() if let rowOfOrder = rowOfOrder { textFieldService.text = rowOfOrder.service?.name textFieldSum.text = String(rowOfOrder.sum) } else { rowOfOrder = RowOfOrder() } } func saveRow() { if let rowOfOrder = rowOfOrder { rowOfOrder.sum = Float(textFieldSum.text!)! CoreDataManager.instance.saveContext() } } }
NSFetchRequest
, now let's take a closer look at it. Let's immediately create a new Table View Controller , create and assign a new class to it ( ReportTableViewController
based on UITableViewController
).NSFetchRequest
an example of a simple report that will display a list of documents executed but not paid for, sorted by date. For this we will use two powerful tools that we have NSFetchRequest
:NSSortDescriptor
- to sort the dataNSPredicate
- to set different selection conditions (filter). var fetchRequest = NSFetchRequest(entityName: "Order") // Sort Descriptor let sortDescriptor = NSSortDescriptor(key: "date", ascending: true) fetchRequest.sortDescriptors = [sortDescriptor] return fetchRequest }()
NSSortDescriptor
), passing it to the constructor a string containing the name of the sort field, and specify the desired sort direction ( ascending
: true
- ascending, false
- descending). Notice that NSFetchRequest
we pass the sort object as an array to the object. What does it mean?Yes, this is exactly - we can pass several sorting rules simultaneously in the form of an array. var fetchRequest:NSFetchRequest = { var fetchRequest = NSFetchRequest(entityName: "Order") // Sort Descriptor let sortDescriptor1 = NSSortDescriptor(key: "date", ascending: true) let sortDescriptor2 = NSSortDescriptor(key: "customer.name", ascending: true) fetchRequest.sortDescriptors = [sortDescriptor1, sortDescriptor2] return fetchRequest }()
// Predicate let predicate = NSPredicate(format: "%K == %@", "made", true) fetchRequest.predicate = predicate
NSSortDescriptor
, use composite fields ("through the point"). But you cannot use several predicates at the same time; instead, you should use a more complex condition in a single predicate. With this in mind, the final definition of the predicate in our report will be as follows: var fetchRequest:NSFetchRequest = { var fetchRequest = NSFetchRequest(entityName: "Order") // Sort Descriptor let sortDescriptor1 = NSSortDescriptor(key: "date", ascending: true) let sortDescriptor2 = NSSortDescriptor(key: "customer.name", ascending: true) fetchRequest.sortDescriptors = [sortDescriptor1, sortDescriptor2] // Predicate let predicate = NSPredicate(format: "%K == %@ AND %K == %@", "made", true, "paid", false) fetchRequest.predicate = predicate return fetchRequest }()
// ReportTableViewController.swift // core-data-habrahabr-swift import UIKit import CoreData class ReportTableViewController: UITableViewController { var fetchRequest:NSFetchRequest = { var fetchRequest = NSFetchRequest(entityName: "Order") // Sort Descriptor let sortDescriptor1 = NSSortDescriptor(key: "date", ascending: true) let sortDescriptor2 = NSSortDescriptor(key: "customer.name", ascending: true) fetchRequest.sortDescriptors = [sortDescriptor1, sortDescriptor2] // Predicate let predicate = NSPredicate(format: "%K == %@ AND %K == %@", "made", true, "paid", false) fetchRequest.predicate = predicate return fetchRequest }() var report: [Order]? override func viewDidLoad() { super.viewDidLoad() do { report = try CoreDataManager.instance.managedObjectContext.executeFetchRequest(fetchRequest) as? [Order] } catch { print(error) } } // MARK: - Table View Data Source override func numberOfSectionsInTableView(tableView: UITableView) -> Int { return 1 } override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { if let report = report { return report.count } else { return 0 } } override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { let cell = UITableViewCell() if let report = report { let order = report[indexPath.row] let formatter = NSDateFormatter() formatter.dateFormat = "MMM d, yyyy" let nameOfCustomer = (order.customer == nil) ? "-- Unknown --" : (order.customer!.name!) cell.textLabel?.text = formatter.stringFromDate(order.date) + "\t" + nameOfCustomer } return cell } }
Core Data
and obtained, in a relatively short time, a fully functional application. The design, of course, at least asks for refinement, but this publication had a different goal. It is worth noting once again that all direct work with data, including the organization of the data warehouse and all possible checks for consistency, is hidden “under the hood” Core Data
, we almost did not think about it, but worked with managed objects as with ordinary OOP objects.Core Data
, which, in my opinion, are necessary for any iOS developer. It is great if you stopped being afraid and, at least a little bit, fell in love Core Data
. Thanks for attention.Source: https://habr.com/ru/post/304586/
All Articles