DEV Community

Avelyn Hyunjeong Choi
Avelyn Hyunjeong Choi

Posted on • Edited on

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

Image of Timescale

🚀 pgai Vectorizer: SQLAlchemy and LiteLLM Make Vector Search Simple

We built pgai Vectorizer to simplify embedding management for AI applications—without needing a separate database or complex infrastructure. Since launch, developers have created over 3,000 vectorizers on Timescale Cloud, with many more self-hosted.

Read more →

Top comments (0)

Sentry image

See why 4M developers consider Sentry, “not bad.”

Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

Learn more