Avelyn Hyunjeong Choi
Posted on November 15, 2023
Core Data Stack
- Managed object Model(MOM): graphical representation of the data model used by your application
- Managed Object Context(MOC): object managing the objects in memory by CRUD operations
- Persistent Store Coordinator(PSC): object that is responsible for coordinating interaction between MOC and the persistent store
- Select the
Emp
Entity - Set the
codegen
toManual/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()
}
}
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
Creating Main View Controller with Table View
- In the
viewWillAppear
method of theMain 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 ourCoreDataHandler
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)
}
}
}
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 isnull
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)
}
}
}
}
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 }
}
CreateUpdateVC Screen
💖 💪 🙅 🚩
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.