Core Data with Table Views Part I

avelynhc

Avelyn Hyunjeong Choi

Posted on November 15, 2023

Core Data with Table Views Part I

Core Data Stack

Core Data Stack

  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

  • 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
// 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()
  }
}
Enter fullscreen mode Exit fullscreen mode

FYI, How to access a static variable

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
Enter fullscreen mode Exit fullscreen mode

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
// 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)
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

MainVC Screen
MainVC Screen

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
// 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)
      }
    }
  }  
}
Enter fullscreen mode Exit fullscreen mode

Extension on UIView

// 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 }
}
Enter fullscreen mode Exit fullscreen mode

CreateUpdateVC Screen

CreateUpdateVC Screen

update screen

💖 💪 🙅 🚩
avelynhc
Avelyn Hyunjeong Choi

Posted on November 15, 2023

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

Sign up to receive the latest update from our blog.

Related