Today, you will implement these tasks in the SwiftCoreDataFromScratch iOS project you worked on last week:
- Edit a Managed Object in the database
- Prevent the user of the Add Book View from inserting duplicate managed objects in the app’s database file
Before You Begin
Now, before implementing above tasks in the project, do these tasks first:
- Use the Source Control menu to create a new branch called, “update-managed-object” from the fetch-and-display-managed-object branch.
- Click the BooksViewController.swift file and delete the debug code in the prepareForSegue() function.
- Import the CoreData module in the BookViewController.swift file.
- Create a constant called context and initialize it with the AppDelegate’s managedObjectContext variable.
The top portion of the BooksViewController class should look like this now:
import UIKit import CoreData class AddViewController: UIViewController { @IBOutlet weak var bookTitle: UITextField! @IBOutlet weak var authorName: UITextField! @IBOutlet weak var photoUrl: UITextField! @IBOutlet weak var imageView: UIImageView! @IBOutlet weak var textView: UITextView! let context = (UIApplication.sharedApplication().delegate as AppDelegate).managedObjectContext! var bookObject: Book!
Edit a Managed Object in The Database
To edit a managed object in the app’s database file, you’ll have to do the following:
- Pass the table view cell’s managed object to the AddBookViewController class.
- load the passed managed object’s properties in the Add Book View’s text fields.
- Updated the passed managed object’s properties in the context, then save the context’s managed object in the app’s database file.
Believe it or not, you’ve already done the first task. It was done last week, when you entered code in the BooksViewController.swift file’s prepareForSegue() function to pass the selected managed object to the AddBookViewController class file’s bookObject variable.
Now, to load the managed object’s properties in the Add Book View’s text fields, add this code below existing code, in the AddBookViewController.swift file’s viewDidLoad() function:
// Load the view's text fields with the bookObject's properties if bookObject != nil { bookTitle.text = bookObject.bookTitle authorName.text = bookObject.authorName photoUrl.text = bookObject.photoUrl }
To save changes made in the text fields, in the database file; modify the AddBookViewController.swift file’s doneButtonTapped() code as shown below:
IBAction func doneButtonTapped(sender: AnyObject) { // Dismiss the keyboard bookTitle.resignFirstResponder() authorName.resignFirstResponder() photoUrl.resignFirstResponder() // Make sure required text fields aren't empty if bookTitle.text.isEmpty || authorName.text.isEmpty { textView.text = "Both the Book Title and the Author Name is required" // Re-initialize the view's text fields with the previously selected manage object's properties bookTitle.text = bookObject.bookTitle authorName.text = bookObject.authorName photoUrl.text = bookObject.photoUrl return // Don't exiecute remaining code in the function } if bookObject == nil { insertManagedObject() } else { updateManagedObject() } }
A new function was called in the doneButtonTapped() function. Here’s the code to implement it:
// This function update an existing managedObject in the database then dismiss the view func updateManagedObject() { // Reset the bookObject's properties with values entered in the text fields bookObject.bookTitle = bookTitle.text bookObject.authorName = authorName.text bookObject.photoUrl = photoUrl.text var savingError: NSError? // Update the managedObject in the database file if !context.save(&savingError) { // Display error message textView.text = "Failed to save the context with error = \(savingError?.localizedDescription)" } else { // Dismiss the AddViewController's view navigationController!.popViewControllerAnimated(true) } }
Now, you have to add code in the BooksViewController.swift file to refresh the table view so changes made to the previously selected managed object appear in the table view. Here is the code to add in the BooksViewController.swift file:
override func viewWillAppear(animated: Bool) { super.viewWillAppear(false) // Refresh the tableView tableView.reloadData() }
Test The Edit Managed Object Code
Go ahead and run the app in the iPhone 5s Simulator and select the book you want to update. For example; click the first cell in the Books View’s table view. When you see the AddBookView, change one or all of the view’s text fields, then click the navigation bar’s Done button. The app updated the managed object in the database, then return you to the Books View.
Preventing Duplicate Entry in The Database
Preventing the user of the Add Book View from inserting duplicate entry (managed object) in the app’s database file is something you should implement in your Core Data project. As of now, the SwiftCoreDataFromScratch project allow you to enter the same managed object in the database file. Take a look at the first image in above table to see what I mean.
Now, click the AddBookViewController.swift file and enter this code in the doneButtonTapped() function.
@IBAction func doneButtonTapped(sender: AnyObject) { // Dismiss the keyboard bookTitle.resignFirstResponder() authorName.resignFirstResponder() photoUrl.resignFirstResponder() // Make sure required text fields aren't empty if bookTitle.text.isEmpty || authorName.text.isEmpty { textView.text = "Both the Book Title and the Author Name is required" // Re-initial the view's text fields with the previously selected manage object's properties bookTitle.text = bookObject.bookTitle authorName.text = bookObject.authorName photoUrl.text = bookObject.photoUrl return // Don't execute remaining code in the function } // Check for duplicate bookTitle in the database if dbFieldExists(bookTitle.text, fieldNumber: 1) { textView.text = "Sorry, that Book Title already exists in the database." return // Don't execute remaining code in the function } // Check for duplicate photoUrl in the database if dbFieldExists(photoUrl.text, fieldNumber: 2) { textView.text = "Sorry, that Photo Url already exists in the database." return // Don't execute remaining code in the function } if bookObject == nil { insertManagedObject() } else { updateManagedObject() } }
Notice how the dbFieldExists() function was called twice in the doneButtonTapped(). Here is the code to implement it:
func dbFieldExists(fieldName: String, fieldNumber: Int) -> Bool { // Empty out this object variable fetchedResultsController = nil // Setup the fetch request let fetchRequest = NSFetchRequest(entityName: "Book") fetchRequest.fetchLimit = 5 // Setup the fetchRequest's predicate (query) if fieldNumber == 1 { fetchRequest.predicate = NSPredicate(format: "bookTitle = [cd] %@", fieldName) } else if fieldNumber == 2 { fetchRequest.predicate = NSPredicate(format: "photoUrl = [cd] %@", fieldName) } // Set up the fetchRequest's sortDescriptor let sortDescriptor = NSSortDescriptor(key: "bookTitle", ascending: true) fetchRequest.sortDescriptors = [sortDescriptor] // Pass the fetchRequest and the context as parameters to the fetchedResultController fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext:context, sectionNameKeyPath: nil, cacheName: nil) // Execute the fetchRequest or display an error message in the Debugger console var error: NSError? = nil if (!fetchedResultsController.performFetch(&error)) { println("Error: \(error?.localizedDescription)") } // Put the total number of managed objects the fetchRequest returned in a constant let managedObjectsFound = fetchedResultsController.fetchedObjects!.count if managedObjectsFound > 0 { // The bookTitle/photoUrl does exists in the database return true } // The bookTitle/photoUrl doesn't exists in the database return false }
Above function query the database to see if the bookTitle or the photoUrl exists. Also, notice how the query condition was setup in the predicate statement. I’m pretty sure you’ll agree with me, this is a pretty nifty function.
Test The Code
Run the app again in the iPhone 5s simulator and test the dbFieldExists() function’s code. As you can see, the app won’t let you add the same book title or photo url in the database. Use the Source Control menu to commit changes you’ve made to the project, in the get repository.
Awesome! You’ve reached the end of the third workshop in the Swift Core Data From Scratch tutorial. Comments are welcomed. Next week, I will bring you the fourth workshop. Until then, happy coding! 🙂