Table View Controller with Core Data and Navigation Controller in Swift

yvzfth

Fatih Yavuz

Posted on June 13, 2023

Table View Controller with Core Data and Navigation Controller in Swift

This document provides an introduction to creating a table view controller in Swift using Core Data and a navigation controller. The combination of these technologies allows you to create a list-based interface with persistent data storage and navigation capabilities. This setup is commonly used in iOS apps to display and manage a collection of items.

Prerequisites

Before proceeding with this tutorial, ensure that you have the following:

  • Xcode installed on your macOS machine.
  • Basic knowledge of Swift programming language.
  • Familiarity with Core Data concepts.
  • Understanding of iOS app development using Storyboard.

Overview

In this tutorial, we will create a table view controller that displays a list of items using Core Data for data persistence. Each item will have attributes such as a name, artist, and year. The table view cells will present the item names, and tapping on a cell will navigate to a details view to view and edit the item's information.

To implement this, we'll use the following components:

  • UITableView to display the list of items.
  • Core Data framework to store and retrieve the item data.
  • NSFetchedResultsController to efficiently manage and update the table view's data.
  • Navigation controller to handle navigation between the table view and details view.

Steps

  1. Create a new Swift project in Xcode.
  2. Set up Core Data in your project by adding an .xcdatamodeld file and defining your entity with the required attributes (name, artist, year).
  3. Design the table view controller scene in Interface Builder by adding a UITableViewController subclass.
  4. Implement the Core Data stack and create a managed object context in the app delegate.
  5. Implement the fetched results controller in the table view controller to fetch and manage the data from Core Data.
  6. Set up the table view data source methods in the table view controller to display the fetched data in the cells.
  7. Add a navigation controller to the storyboard and embed the table view controller in it.
  8. Create a segue from the table view cell to the details view controller for navigation.
  9. Implement the details view controller to display and edit the item's information.
  10. Add functionality to the table view controller for adding, deleting, and updating items using Core Data.
  11. Test your app on the iOS Simulator or a physical device to verify its functionality.
class TableViewController : UITableViewController
Enter fullscreen mode Exit fullscreen mode

The document contains Swift code defining a TableViewController as a subclass of UITableViewController.

var nameArray = [String]()
var idArray = [UUID]()
Enter fullscreen mode Exit fullscreen mode

In this particular line of code, we initialize the nameArray and idArray to empty arrays. This means that the arrays has no elements in it yet, but can be populated with String and UUID elements later in the code.

override func viewDidLoad() {
  super.viewDidLoad()

    //tableView config
    tableView.dataSource = self
    tableView.delegate = self
}
Enter fullscreen mode Exit fullscreen mode

A Swift code snippet that configures a tableView's dataSource and delegate in the viewDidLoad() method.

// Table View Rows
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
  return nameArray.count
}
// Table View Row Texts
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
  let cell = UITableViewCell()
  cell.textLabel?.text = nameArray[indexPath.row]
  return cell
}
// On Row selection
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    // Set id to tappedId to access tappedId value in prepare segue func  
    tappedId = idArray[indexPath.row]
  self.performSegue(withIdentifier: "toDetailVC", sender: nil)
}
Enter fullscreen mode Exit fullscreen mode

This is a Swift code that sets up a table view with rows and row texts. The function tableView(_:numberOfRowsInSection:) returns the number of rows in a section, which is equal to the number of elements in the nameArray. The function tableView(_:cellForRowAt:) returns a table view cell that displays the name of an element in the nameArray at the corresponding indexPath.row. The function tableView(_:didSelectRowAt:) is called when a cell is selected, it sets the variable tappedId to the value at idArray[indexPath.row] and performs a segue to the view controller with identifier "toDetailVC".

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
  if segue.identifier == "toDetailVC" {
    let destinationVC = segue.destination as! DetailViewController
    destinationVC.selectedId = tappedId
    tappedId = nil
  }
}
Enter fullscreen mode Exit fullscreen mode

This is a Swift code snippet that overrides the prepare method in a view controller. It checks if the segue identifier is "toDetailVC" and if so, it sets the selectedId property of the destination view controller to tappedId and then sets tappedId to nil.

override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
    if editingStyle == .delete {
        nameArray.remove(at: indexPath.row)
    }
}
Enter fullscreen mode Exit fullscreen mode

Removes a row from a table view when the user swipes left and taps "Delete." The nameArray is the data source for the table view, and indexPath.row is the index of the row to be removed.

Using Core Data In Table View

@objc func getData() {

  nameArray.removeAll(keepingCapacity: false)
  idArray.removeAll(keepingCapacity: false)

  let appDelegate = UIApplication.shared.delegate as! AppDelegate
  let context = appDelegate.persistentContainer.viewContext

  let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Paintings")
  fetchRequest.returnsObjectsAsFaults = false

  do {
    let results = try context.fetch(fetchRequest)
    if results.count > 0 {
      for result in results as! [NSManagedObject] {
        if let name = result.value(forKey: "name") as? String {
          self.nameArray.append(name)
        }

        if let id = result.value(forKey: "id") as? UUID {
          self.idArray.append(id)
        }

        self.tableView.reloadData()             
      }
    }
  } catch {
    print("error")
  }
}
Enter fullscreen mode Exit fullscreen mode

This code snippet retrieves data from a Core Data entity named "Paintings" and populates two arrays (nameArray and idArray) with the fetched data. Here's a breakdown of the code:

  1. The getData() function is marked with @objc to make it accessible from Objective-C code.
  2. The nameArray and idArray are cleared using removeAll(keepingCapacity:) method, ensuring a clean slate before fetching new data.
  3. The AppDelegate instance is obtained using UIApplication.shared.delegate. This allows access to the persistent container and the Core Data context.
  4. A NSFetchRequest is created with the entity name "Paintings" to specify the entity from which data needs to be fetched. returnsObjectsAsFaults is set to false to ensure that the fetched objects are fully realized (not just faulted) and contain the actual attribute values.
  5. The fetch request is executed using context.fetch(fetchRequest), and the results are stored in the results array.
  6. If there are results (results.count > 0), the code iterates through each fetched object.
  7. Inside the loop, the code retrieves the "name" attribute value using value(forKey:) and casts it to a String. If the cast is successful, the name is appended to the nameArray.
  8. Similarly, the code retrieves the "id" attribute value as a UUID and appends it to the idArray.
  9. Finally, after all the data is fetched and appended to the arrays, the tableView is reloaded using tableView.reloadData() to reflect the updated data in the UI.
  10. In case an error occurs during the fetch operation, the catch block is executed and "error" is printed to the console.

This code assumes that you have a Core Data entity named "Paintings" with attributes "name" (String) and "id" (UUID). The nameArray and idArray are assumed to be instance variables of the class containing this code.

Note: You should call getData() in the viewDidLoad method to ensure data is loaded when the app opens.

Note: It's important to handle any errors that may occur during Core Data fetch operations. Printing "error" to the console is a minimal error handling approach for demonstration purposes. In a production environment, you should consider handling errors more gracefully, such as displaying an alert to the user or logging detailed error information.

override func viewWillAppear(_ animated: Bool) {
    NotificationCenter.default.addObserver(self, selector: #selector(getData), name: NSNotification.Name(rawValue: "newData"), object: nil)
}
Enter fullscreen mode Exit fullscreen mode

This code snippet adds an observer to the default NotificationCenter in the viewWillAppear method of a view controller. The observer listens for a specific notification named "newData" and calls the getData() method when the notification is received.

Update cell deletion method from table view using Core Data

override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
  if editingStyle == .delete {

    let appDelegate = UIApplication.shared.delegate as! AppDelegate
    let context = appDelegate.persistentContainer.viewContext

    let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Paintings")

    let idString = idArray[indexPath.row].uuidString

    fetchRequest.predicate = NSPredicate(format: "id = %@", idString)

    fetchRequest.returnsObjectsAsFaults = false

    do {
        let results = try context.fetch(fetchRequest)
      if results.count > 0 {

        for result in results as! [NSManagedObject] {

          if let id = result.value(forKey: "id") as? UUID {

            if id == idArray[indexPath.row] {
              context.delete(result)
              nameArray.remove(at: indexPath.row)
              idArray.remove(at: indexPath.row)
              self.tableView.reloadData()

              do {
                try context.save()

              } catch {
                print("error")
              }

              break
            }
          } 

        }
        }
    } catch {
        print("error")
    }

  }
}
Enter fullscreen mode Exit fullscreen mode

This code snippet is an implementation of the tableView(_:commit:forRowAt:) method, which is a delegate method for handling editing actions in a UITableView. Specifically, this implementation handles the delete action for a table view cell. Here's an explanation of the code:

  1. The method is called when the user performs a commit action, such as swiping on a table view cell and tapping the delete button.
  2. The if editingStyle == .delete condition checks if the editing style is set to .delete, indicating that the user wants to delete the cell.
  3. The code retrieves the AppDelegate instance and the Core Data context from the shared application delegate.
  4. A fetchRequest is created with the entity name "Paintings" to specify the entity from which data needs to be fetched.
  5. The idString variable is assigned the UUID string value of the id at the corresponding row index in the idArray.
  6. A predicate is set on the fetch request using NSPredicate to specify the condition for the fetch request. In this case, it filters the objects based on their id attribute matching the idString.
  7. returnsObjectsAsFaults is set to false to ensure the fetched objects are fully realized (not faulted) and contain the actual attribute values.
  8. The fetch request is executed using context.fetch(fetchRequest), and the results are stored in the results array.
  9. If there are results (results.count > 0), the code enters the loop.
  10. Inside the loop, the code checks if the id attribute of the fetched object matches the id at the corresponding row index in the idArray.
  11. If there is a match, it means the correct object has been found for deletion. The code proceeds to delete the object from the Core Data context using context.delete(result).
  12. The corresponding elements in nameArray and idArray are also removed at the same index to keep the data in sync.
  13. The table view is reloaded using tableView.reloadData() to reflect the updated data in the UI.
  14. The changes are saved to the Core Data context using context.save().
  15. If any error occurs during the deletion or saving process, the catch block is executed, and "error" is printed to the console.

This code assumes you have a Core Data entity named "Paintings" with an attribute named "id" of type UUID. The nameArray and idArray are assumed to be instance variables of the class containing this code.

It's important to handle any errors that may occur during Core Data operations more gracefully in a production environment, such as displaying an alert to the user or logging detailed error information.

Navigation Bar Add Button

navigationController?
    .navigationBar.topItem?
    .rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonItem.SystemItem.add,
                                                                              target: self, 
                                                                                action: #selector(addTapped))
Enter fullscreen mode Exit fullscreen mode

This code snippet sets the rightBarButtonItem of the navigation bar in a view controller's navigationBar to a system-defined "Add" button. When the "Add" button is tapped, it triggers the addTapped method on the current view controller.

Note: You should call this line of code in the viewDidLoad method to ensure button is rendered when the app opens.

@objc func addTapped(){
  performSegue(withIdentifier: "toDetailsVC", sender: nil)
}
Enter fullscreen mode Exit fullscreen mode

This code snippet is an implementation of the addTapped method, which is called when the "Add" button is tapped in the navigation bar. The method performs a segue to navigate to another view controller with the identifier "toDetailsVC".

Conclusion

By following this document, you have learned how to create a table view controller with Core Data and a navigation controller in Swift. This setup enables you to build a list-based interface with persistent data storage and navigation capabilities. You can now extend the functionality of your app by adding additional features, such as search, sorting, and filtering based on the stored data.

Remember to consult the official Apple documentation and other online resources for further details and best practices while working on your iOS app development projects. Happy coding!

💖 💪 🙅 🚩
yvzfth
Fatih Yavuz

Posted on June 13, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related