Table View Controller with Core Data and Navigation Controller in Swift
Published: 2023-06-13 01:09:17

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.

```swift
class TableViewController : UITableViewController
```

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

```swift
var nameArray = [String]()
var idArray = [UUID]()
```

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.

```swift
override func viewDidLoad() {
  super.viewDidLoad()

	//tableView config
	tableView.dataSource = self
	tableView.delegate = self
}
```

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

```swift
// 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)
}
```

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".

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

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`.

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

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

```swift
@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")
  }
}
```

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.

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

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

```swift
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")
    }

  }
}
```

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

```swift
navigationController?
	.navigationBar.topItem?
	.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonItem.SystemItem.add,
																			  target: self, 
																				action: #selector(addTapped))
```

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.

```swift
@objc func addTapped(){
  performSegue(withIdentifier: "toDetailsVC", sender: nil)
}
```

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!