Core Data with Table Views Part I
Tags: swift, mobile, beginners
Published: 2023-11-15 15:29:09

**Core Data Stack**

![Core Data Stack](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/a042401p21b2bkrqg7yr.png)

1. Managed object Model(MOM): graphical representation of the data model used by your application
2. Managed Object Context(MOC): object managing the objects in memory by CRUD operations
3. Persistent Store Coordinator(PSC): object that is responsible for coordinating interaction between MOC and the persistent store

**Creating an Entity**
![Creating an Entity](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uthvmf9gm39jjrlmf3xp.png)

- Select the `Emp` Entity
- Set the `codegen` to `Manual/None` from the attributes inspector
- Go to `Editor > Create NSManagedObject Subclass` and complete the wizard
- You will have 2 additional files in your project now representing the Model `Emp`.

**Creating a separate model class for handling CRUD with Core Data**
- uses `Singleton` design pattern

```swift
// CoreDataHandler

import Foundation
import UIKit
import CoreData

class CoreDataHandler {
  
  /// shared instance of class
  static let shared = CoreDataHandler()
  
  let appDelegate = UIApplication.shared.delegate as! AppDelegate
  var context: NSManagedObjectContext?
  
  private init() {
    context = appDelegate.persistentContainer.viewContext
  }
  
  func saveContext() {
    appDelegate.saveContext()
  }
  
  func insert(name:String, age:Int, phone:String, completion: @escaping () -> Void) {
    
    let emp = Emp(context: context!)
    emp.name = name
    emp.age = Int64(age)
    emp.phone = phone
    
    saveContext()
    completion()
  }
  
  func update(emp:Emp, name:String, age:Int, phone:String, completion: @escaping () -> Void) {
    
    emp.name = name
    emp.age = Int64(age)
    emp.phone = phone
    
    saveContext()
    completion()
  }
  
  /// Returns an array of Emp (Core Data obj)
  func fetchData() -> Array<Emp> {
    let fetchRequest: NSFetchRequest<Emp> = Emp.fetchRequest()
    do {
      let emp = try context?.fetch(fetchRequest)
      return emp!
    } catch {
      print(error.localizedDescription)
      // Returning an empty Array - Error Handling
      let emp = Array<Emp>()
      return emp
    }
  }
  
  func deleteData(for emp:Emp, completion: @escaping () -> Void) {
    
    context!.delete(emp)
    saveContext()
    completion()
  }
}
```

_FYI, How to access a static variable_

```swift
class Person {
    var name = "Abc"
    static var address = "123 Dr"
}

let p = Person()
// access variable
p.name = ..
Person.address = ... // as it's a static variable
```

**Creating Main View Controller with Table View**
- In the `viewWillAppear` method of the `Main View Controller`, you should fetch the data from the database and populate the table view.
- To fetch data from Core Data, you can use the helper function `fetchData()` that we created in our `CoreDataHandler` class

```swift
// MainVC

import UIKit

class MainVC: UIViewController {
  
  private var empArray = Array<Emp>() {
    didSet{myTableView.reloadData()}
  }
  
  private let myTableView = UITableView()
  
  override func viewDidLoad() {
    super.viewDidLoad()
    
    title = "Employees"
    
    view.addSubview(myTableView)
    
    let addItem = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(addButtonTapped))
    navigationItem.setRightBarButton(addItem, animated: true)
    
    setupTableView()
  }
  
  override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    
    empArray = CoreDataHandler.shared.fetchData()
  }
  
  override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    myTableView.frame = view.bounds
  }
  
  @objc private func addButtonTapped() {
    let vc = CreateUpdateVC()
    navigationController?.pushViewController(vc, animated: true)
  }
}

extension MainVC: UITableViewDataSource, UITableViewDelegate {
  
  private func setupTableView() {
    myTableView.register(UITableViewCell.self, forCellReuseIdentifier: "empCell")
    myTableView.dataSource = self
    myTableView.delegate = self
  }
  
  func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return empArray.count
  }
  
  func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "empCell", for: indexPath)
    
    let content = "\(empArray[indexPath.row].name!) \t | \t \(empArray[indexPath.row].age) \t | \t \(empArray[indexPath.row].phone!)"
    
    cell.textLabel!.text = content
    return cell
  }
  
  func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    print(empArray[indexPath.row])
    
    let vc = CreateUpdateVC()
    vc.emp = empArray[indexPath.row]
    navigationController?.pushViewController(vc, animated: true)
  }
  
  func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
    
    CoreDataHandler.shared.deleteData(for: empArray[indexPath.row]) { [weak self] in
      print("Data deleted")
      //self?.fetchData()
      self?.empArray.remove(at: indexPath.row)
      //tableView.deleteRows(at: [indexPath], with: .automatic)
    }
  }
}
```

**MainVC Screen**
![MainVC Screen](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/shtqa525ggybz8mrmrnq.png)

**Creating Create/Update View Controller**
- When the user saves the record, you should update the database and then return to the Main View Controller.
- If the `emp` variable is `null` then you are creating a new record, else you are updating an existing record

```swift
// CreateUpdateVC

import UIKit

class CreateUpdateVC: UIViewController {
  
  var emp:Emp?
  
  private let nameTextField:UITextField = {
    let textField = UITextField()
    textField.placeholder = "Full Name"
    textField.textAlignment = .center
    textField.borderStyle = .roundedRect
    textField.backgroundColor = .systemFill
    textField.autocorrectionType = .no
    return textField
  }()
  
  private let ageTextField:UITextField = {
    let textField = UITextField()
    textField.placeholder = "Age"
    textField.textAlignment = .center
    textField.borderStyle = .roundedRect
    textField.backgroundColor = .systemFill
    return textField
  }()
  
  private let phoneTextField:UITextField = {
    let textField = UITextField()
    textField.placeholder = "Phone Number"
    textField.textAlignment = .center
    textField.borderStyle = .roundedRect
    textField.backgroundColor = .systemFill
    return textField
  }()
  
  private let saveButton:UIButton = {
    let button = UIButton()
    button.setTitle("Save", for: .normal)
    button.backgroundColor = .systemGreen
    button.layer.cornerRadius = 6
    return button
  }()
  override func viewDidLoad() {
    super.viewDidLoad()
    
    view.backgroundColor = .systemBackground
    saveButton.addTarget(self, action: #selector(saveButtonTapped), for: .touchUpInside)
    
    view.addSubview(nameTextField)
    view.addSubview(ageTextField)
    view.addSubview(phoneTextField)
    view.addSubview(saveButton)
    
    if let emp = emp {
      nameTextField.text = emp.name
      ageTextField.text = String(emp.age)
      phoneTextField.text = emp.phone
    }
  }
  
  override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    
    nameTextField.frame = CGRect(x: 40, y: view.safeTop + 40, width: view.width - 80, height: 40)
    ageTextField.frame = CGRect(x: 40, y: nameTextField.bottom + 20, width: view.width - 80, height: 40)
    phoneTextField.frame = CGRect(x: 40, y: ageTextField.bottom + 20, width: view.width - 80, height: 40)
    saveButton.frame = CGRect(x: 40, y: phoneTextField.bottom + 20, width: view.width - 80, height: 40)
  }
  
  @objc private func saveButtonTapped() {
    
    let name = nameTextField.text!
    let age = Int(ageTextField.text!)!
    let phone = phoneTextField.text!
    
    if let emp = emp {
      
      CoreDataHandler.shared.update(emp: emp, name: name, age: age, phone: phone) { [weak self] in
        self?.navigationController?.popViewController(animated: true)
      }
    } else {
      
      CoreDataHandler.shared.insert(name: name, age: age, phone: phone) { [weak self] in
        self?.navigationController?.popViewController(animated: true)
      }
    }
  }  
}
```

**Extension on UIView**

```swift
// Extensions

import Foundation
import UIKit

extension UIView {
  public var width: CGFloat { return frame.size.width }
  public var height: CGFloat { return frame.size.height }
  public var top: CGFloat { return frame.origin.y }
  public var bottom: CGFloat { return frame.origin.y + frame.size.height }
  public var left: CGFloat { return frame.origin.x }
  public var right: CGFloat { return frame.origin.x + frame.size.width }
  public var safeTop: CGFloat { return safeAreaInsets.top }
}
```

**CreateUpdateVC Screen**

![CreateUpdateVC Screen](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5tpliear1imz1qqkkbq9.png)

![update screen](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/caco1uuv7bbp0plkn5d4.png)