CoreData에 데이터 저장하는 것에 이어
새로운 TableViewController를 사용해 CoreData에 저장된 데이터를 tableView에 출력하고,
해당 tableView 위에 버튼을 만들어 버튼 클릭시 CoreData에 저장된 데이터를 삭제해보려고 합니다! 💪🏻
TableView UI 만들기
우선 TableView의 UI부터 만들어 보려고 합니다!
메인화면에서 navigationController를 연결하여 navigationItem안의 버튼 클릭시 modal로 TableViewController가 뜨도록 구현을 하였습니다.
WishList를 보여주는 View도 29CM의 어플의 장바구니를 보고 비슷하게 UI를 그려주었어요.
여러 항목을 선택하는 체크 버튼이 따로 필요가 없어서 삭제 버튼은 brand 명칭 옆으로 옮겨주었어요!
CoreData에 저장된 데이터를 가져오는 함수
CoreData에 저장된 데이터를 가져오는 함수를 이전에 만들어 두었던 ProductCoreDataManager
구조체 안에 만들어 줍니다! 이번에는 모든 데이터를 가져오면 되어서 따로 데이터의 조건이 들어간 요청서를 만들지는 않았어요. 다만, id의 오름차순으로 출력이 될 수 있도록 NSSortDescriptor
는 사용하여 데이터를 정렬하였습니다.
mutating func getProductCoredata() -> [ProductData] {
var ProductList = [ProductData]()
guard let context = context else {
return ProductList
}
let fetchRequest: NSFetchRequest<ProductData> = ProductData.fetchRequest()
let sortDescriptor = NSSortDescriptor(key: "id", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptor]
do {
ProductList = try context.fetch(fetchRequest)
} catch {
print("Error fetching todo list data: \(error)")
}
return ProductList
}
context가 구조체의 저장 프로퍼티이므로 함수에 mutating
키워드를 넣어주어야 했어요!
fetch
함수를 사용해 데이터를 리턴해주는 함수를 만들었습니다!
TableViewController
TableView를 사용하기 위해 필수적으로 추가해야하는 두가지 함수로 TableView에 데이터를 추가해봅시다!
그 전에! ProductCoreDataManager
를 통해 함수를 불러올 예정이니 인스턴스먼저 정의내렸습니다.
var coreDataManager = ProductCoreDataManager.shared
1) tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return coreDataManager.getProductCoredata().count
}
coreDataManager
에게 접근하여 해당 데이터 배열의 갯수를 return 값으로 넣어줍니다!
2) tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "wishItemCell", for: indexPath) as! TableViewCell
let productDataList = coreDataManager.getProductCoredata()
let productData = productDataList[indexPath.row]
cell.wishCartBrandLabel.text = productData.brand
cell.wishCartPriceLabel.text = ViewController().setPriceLabel(price: Int(productData.price))
cell.wishCartTitleLabel.text = "[\(productData.id)] \(productData.title!)"
if let thumbnail = productData.thumbnail {
if let url = URL(string: thumbnail) {
URLSession.shared.dataTask(with: url) { data, response, error in
if let data = data, error == nil {
DispatchQueue.main.async {
cell.wishCartThumnailImageView.image = UIImage(data: data)
}
}
}.resume()
}
}
return cell
}
cell의 identifier
를 사용하여 TableViewCell
과 연결해주고, coreDataManager
에게 접근하여 해당 데이터를 변수에 저장하여 cell과 연결된 Label에 데이터의 값을 할당해줍니다!
이때도 thumbnail의 데이터 값은 url String 형태로 저장이 되어있기때문에 URLSession
통신을 통해서 데이터를 가져와 UIImage로 바꾸어 주었습니다!
[ price 라벨에 천 단위로 ,(콤마) 찍기 ]
main 화면에서도 price 라벨이 나오고 tableViewCell
에서도 price 라벨이 나와 해당 price text에 천 단위로 콤마를 찍기 위해서 ViewController
에 새로운 함수를 하나 만들어주었습니다.
func setPriceLabel(price: Int) -> String {
let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = .decimal
guard let priceString = numberFormatter.string(from: NSNumber(value: price)) else { return "" }
return "$ " + priceString
}
Int 값을 파라미터로 받아와서 NumberFormatter
클래스를 이용하여 천단위로 콤마를 찍도록 구현하였습니다.
위의 cell.wishCartPriceLabel.
text
에서도 viewController
에게 접근하여 해당 함수를 사용하는 것을 볼 수 있습니다~!
TableView 위의 삭제 버튼
tableView 위의 삭제 버튼을 클릭시 해당 데이터가 코어데이터에서 삭제되도록 구현하였습니다.
우선 코어데이터에서 해당 데이터를 삭제하기위한 함수를 하나 만들어주었습니다!
Coredata의 삭제 함수
mutating func deleteCoreData(_ productData: ProductData) {
guard let context = context else {
return
}
context.delete(productData)
do {
try context.save()
} catch {
print("Error deleting todo data: \(error)")
}
}
해당 데이터를 delete()
함수만 사용하여 제거할 수 있어서 무척 간단했어요!
하지만 문제는 cell에서 어떻게 ViewController
에게 해당 함수를 실행하도록 정보를 보내는지..!!
이제 많이 해서 익숙합니다~!ㅎㅎ
Closure를 사용해 cell에서 ViewController에게 삭제 함수 전달하기
저는 사용해본 것 중에 closure
함수를 사용하는 것이 가장 간편하더라구요!
우선 TableViewCell
에 클로저 함수를 정의합니다.
var onDeleteTapped: (() -> Void)?
해당 버튼이 클릭되었을 때, 해당 함수를 호출합니다.
@IBAction func deleteButtonTapped(_ sender: UIButton) {
onDeleteTapped?()
}
그럼 이제 TableViewController
에게 가서 해당 함수를 실행합시다!
아래의 클로저 함수를 몇번째 cell의 데이터인지를 알고 있는 tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
함수 내에서 실행시켜줍니다.
cell.onDeleteTapped = {
self.coreDataManager.deleteCoreData(productData)
tableView.reloadData()
}
데이터가 변환되었으니 다시 TableViewCell
을 그리는 reloadData()
함수도 추가해줍니다.
[ TableViewController의 전체 코드 ]
import UIKit
class TableViewController: UITableViewController {
var coreDataManager = ProductCoreDataManager.shared
override func viewDidLoad() {
super.viewDidLoad()
}
// MARK: - Table view data source
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return coreDataManager.getProductCoredata().count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "wishItemCell", for: indexPath) as! TableViewCell
let productDataList = coreDataManager.getProductCoredata()
let productData = productDataList[indexPath.row]
cell.wishCartBrandLabel.text = productData.brand
cell.wishCartPriceLabel.text = ViewController().setPriceLabel(price: Int(productData.price))
cell.wishCartTitleLabel.text = "[\(productData.id)] \(productData.title!)"
if let thumbnail = productData.thumbnail {
if let url = URL(string: thumbnail) {
URLSession.shared.dataTask(with: url) { data, response, error in
if let data = data, error == nil {
DispatchQueue.main.async {
cell.wishCartThumnailImageView.image = UIImage(data: data)
}
}
}.resume()
}
}
cell.onDeleteTapped = {
self.coreDataManager.deleteCoreData(productData)
tableView.reloadData()
}
return cell
}
}
[ TableViewCell의 전체 코드 ]
import UIKit
class TableViewCell: UITableViewCell {
@IBOutlet weak var wishCartBrandLabel: UILabel!
@IBOutlet weak var wishCartTitleLabel: UILabel!
@IBOutlet weak var wishCartPriceLabel: UILabel!
@IBOutlet weak var wishCartThumnailImageView: UIImageView!
@IBOutlet weak var deleteButton: UIButton!
var onDeleteTapped: (() -> Void)?
override func awakeFromNib() {
super.awakeFromNib()
setDeleteButtonBorder()
}
func setDeleteButtonBorder() {
deleteButton.layer.cornerRadius = 3
deleteButton.clipsToBounds = true
deleteButton.layer.borderWidth = 1
deleteButton.layer.borderColor = UIColor.lightGray.cgColor
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
@IBAction func deleteButtonTapped(_ sender: UIButton) {
onDeleteTapped?()
}
}
[ ProductCoreDataManger의 전체 코드 ]
import Foundation
import UIKit
import CoreData
struct ProductCoreDataManager {
// 데이터 매니저
static let shared = ProductCoreDataManager()
private init() {}
// 앱 델리게이트
let appDelegate = UIApplication.shared.delegate as? AppDelegate
// 임시저장소
lazy var context = appDelegate?.persistentContainer.viewContext
// 엔터티 이름 (코어 데이터에 저장된 객체)
let modelName: String = "ProductData"
// TodoData를 생성하는 메서드
mutating func setProductCoreData(data: Product) {
guard let context = context else { return }
let fetchRequest: NSFetchRequest<ProductData> = ProductData.fetchRequest()
fetchRequest.predicate = NSPredicate(format: "id == %d", data.id)
do {
let results = try context.fetch(fetchRequest)
let newData: ProductData
if results.isEmpty {
// 새로운 객체 생성
guard let entity = NSEntityDescription.entity(forEntityName: modelName, in: context) else { return }
newData = NSManagedObject(entity: entity, insertInto: context) as! ProductData
} else {
// 기존 객체 업데이트
newData = results.first!
}
// 속성 값 설정
newData.id = Int64(data.id)
newData.title = data.title
newData.productDescription = data.description
newData.price = Int64(data.price)
newData.brand = data.brand
newData.category = data.category
newData.discountPercentage = data.discountPercentage
newData.thumbnail = data.thumbnail
// 변경 사항 저장
try context.save()
print(" 데이터가 저장되었습니다.")
} catch {
print("Error updating ProductData: \(error)")
}
}
mutating func getProductCoredata() -> [ProductData] {
var ProductList = [ProductData]()
guard let context = context else {
return ProductList
}
let fetchRequest: NSFetchRequest<ProductData> = ProductData.fetchRequest()
let sortDescriptor = NSSortDescriptor(key: "id", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptor]
do {
ProductList = try context.fetch(fetchRequest)
} catch {
print("Error fetching todo list data: \(error)")
}
return ProductList
}
mutating func deleteCoreData(_ productData: ProductData) {
guard let context = context else {
return
}
context.delete(productData)
do {
try context.save()
} catch {
print("Error deleting todo data: \(error)")
}
}
}
구현화면
오~! 깔끔하게 실행되고 있는 것을 볼 수 있습니다!
다만,, 제가 스크롤뷰로 화면을 구현했는데 스크롤뷰가 되지 않구요!ㅋㅋ
이미지 파일을 여러장 추가하여 스와이프할 수 있도록 구현하면 더 좋을 것 같아요!!
내일은 테이블 뷰를 위로 아래로 스와이프 했을 때, cell이 새로고침되는 부분을 구현할 것 같습니다!