https://explorer89.tistory.com/217
이전에 작성했던 FileManager 사용방법에서 수정할 내용이 있습니다.
이미지를 저장할 때, feedID를 사용하고, 피드 자체를 저장할 때도 feedID를 사용하여 혼용이 있었습니다.
그래서 발생한 문제가, 코어 데이터에 삭제 기능을 추가했음에도 테이블뷰에서는 삭제되어도, 코어데이터에는 남아 있는 에러가 발생했습니다.
🔥 코드에서 feedID가 중복되거나 혼용되어 잘못된 값이 할당되고 있는 것으로 보입니다. feedID는 코어 데이터와 파일 시스템에서 이미지를 구분하기 위해 각 FeedItem에 고유하게 생성된 값이어야 합니다.
🔥 이런 경우, feedID는 피드(FeedItem)를 구분하는 데 사용하고, imageID는 각 이미지를 구분하는 고유한 ID로 활용하여 이미지를 저장하고 불러올 때 명확하게 관리할 수 있습니다.
수정된 saveImages 메서드
//
// FeedStorageManager.swift
//
/*
FileManager를 통해 이미지를 저장하고 해당 이미지 파일 경로를 반환하는 메서드를 작성
*/
import UIKit
class FeedStorageManager {
// Documents 폴더 경로 가져오기
private func getDocumentsDirectory() -> URL {
FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
}
// 이미지 저장
func saveImages(images: [UIImage], feedID: String) -> [String] {
let feedFolder = getDocumentsDirectory().appendingPathComponent(feedID)
// 폴더가 없으면 생성
if !FileManager.default.fileExists(atPath: feedFolder.path) {
try? FileManager.default.createDirectory(at: feedFolder, withIntermediateDirectories: true, attributes: nil)
}
var savedImagePaths: [String] = []
for image in images {
let imageID = UUID().uuidString // 각 이미지의 고유 ID 생성
let fileName = "\(imageID).jpg"
let fileURL = feedFolder.appendingPathComponent(fileName)
if let imageData = image.jpegData(compressionQuality: 1.0) {
try? imageData.write(to: fileURL)
savedImagePaths.append("\(feedID)/\(fileName)") // 상대경로 저장
}
}
return savedImagePaths
}
// 이미지 로드
func loadImages(from relativePaths: [String]) -> [UIImage] {
var images = [UIImage]()
for relativePath in relativePaths {
let fullPath = getDocumentsDirectory().appendingPathComponent(relativePath)
if let image = UIImage(contentsOfFile: fullPath.path) {
images.append(image)
}
}
// print("불러온 이미지", images)
return images
}
// 이미지 삭제
func deleteImages(from paths: [String]) {
for path in paths {
let fileURL = getDocumentsDirectory().appendingPathComponent(path)
try? FileManager.default.removeItem(at: fileURL)
}
}
}
이제 saveFeedItem에서는 feedID를 피드 전체의 식별자로만 사용하고, 이미지를 저장할 때는 각 이미지에 대해 imageID가 추가된 파일 경로를 사용하도록 했습니다.
func saveFeedItem(feedItem: FeedItem) {
let feedEntity = FeedEntity(context: context)
// FeedItem의 데이터 설정
feedEntity.tripLog = feedItem.tripLog
feedEntity.location = feedItem.location
feedEntity.date = feedItem.date
feedEntity.category = feedItem.category
feedEntity.feedID = feedItem.feedID
// 이미지 저장 및 경로 설정
if let images = feedItem.images {
let imagePaths = storageManager.saveImages(images: images, feedID: feedItem.feedID)
feedEntity.imagePaths = imagePaths.joined(separator: ",")
}
do {
try context.save()
print("FeedItem이 성공적으로 저장되었습니다.")
//print("저장된 이미지 경로:", feedEntity.feedID!)
} catch {
print("FeedItem 저장 실패: \(error)")
}
}
func fetchFeedItems() -> [FeedItem] {
let request: NSFetchRequest<FeedEntity> = FeedEntity.fetchRequest()
var feedItems: [FeedItem] = []
do {
let feedEntities = try context.fetch(request)
for entity in feedEntities {
let images: [UIImage]
if let imagePathsString = entity.imagePaths {
let imagePathsArray = imagePathsString.components(separatedBy: ",")
images = storageManager.loadImages(from: imagePathsArray)
} else {
images = []
}
// FeedItem을 생성하고 데이터 할당
let feedItem = FeedItem(
images: images,
tripLog: entity.tripLog,
location: entity.location,
date: entity.date,
category: entity.category,
feedID: entity.feedID ?? UUID().uuidString
)
feedItems.append(feedItem)
print(feedItem.feedID)
}
} catch {
print("데이터 로드 실패: \(error)")
}
return feedItems
}
- feedID 및 imagePathsArray 사용: imagePathsString에서 imagePathsArray로 개별 이미지 경로를 나누고, 각 경로를 loadImages 메서드에 전달하여 이미지를 로드합니다.
- 데이터 할당:
- FeedItem을 생성할 때 feedID를 entity.feedID로 설정해 고유 피드 식별자를 할당합니다.
- feedItem.feedID가 없는 경우 새 UUID를 생성하여 할당합니다.
이제 fetchFeedItems()는 feedID와 imagePathsArray를 통해 FeedItem을 생성하고 데이터를 할당하므로, 각 FeedItem이 개별 피드 및 이미지를 정확히 나타내도록 합니다.
삭제 구현
Core Data와 FileManager 모두 사용하는 경우, 삭제 시 주의할 점이 있습니다.
코어데이터에는 이미지의 경로만 저장되어 있고 실제 이미지 파일은 FileManager에 있으므로, 이미지와 관련된 모든 데이터를 완전히 삭제하려면 다음과 같이 둘 다 삭제하는 것이 좋습니다.
1. 코어데이터의 데이터 삭제
- 코어데이터의 해당 엔티티에서 데이터를 삭제합니다. 이 작업은 이미지의 경로 정보만 삭제되므로, 이후 FileManager에 이미지가 남아있게 됩니다.
2. 파일 매니저의 이미지 삭제
- FileManager에 있는 실제 이미지 파일도 삭제해야 저장 공간이 불필요하게 차지되는 것을 방지할 수 있습니다.
- 이때, Core Data에 저장된 이미지 경로 정보를 사용하여 FileManager에서 정확한 이미지를 찾아 삭제하면 됩니다.
최종 삭제 프로세스
- 코어데이터에서 데이터 삭제:
- 이미지 경로와 관련된 Core Data 데이터를 삭제하여 데이터베이스에서 정보가 사라지도록 합니다.
- 파일 매니저에서 이미지 삭제:
- Core Data에서 삭제한 이미지의 경로를 이용해 FileManager에서 실제 파일을 삭제합니다.
이렇게 처리하면, 이미지 데이터의 중복 저장이나 불필요한 저장 공간 낭비를 방지할 수 있습니다.
파일매니저 내에서 삭제기능 구현
func deleteImages(from paths: [String]) {
for path in paths {
let fileURL = getDocumentsDirectory().appendingPathComponent(path)
try? FileManager.default.removeItem(at: fileURL)
}
}
테이블뷰에서 Core Data의 삭제 기능을 사용하는 방법은 다음과 같습니다. 여기서는 UITableView의 스와이프 액션을 이용해 삭제 기능을 구현하는 방법을 소개하겠습니다.
1. Core Data 삭제 메서드 구현
func deleteFeedItem(feedID: String) {
let request: NSFetchRequest<FeedEntity> = FeedEntity.fetchRequest()
request.predicate = NSPredicate(format: "feedID == %@", feedID)
do {
let results = try context.fetch(request)
if let feedEntity = results.first {
// 이미지 경로 삭제
if let imagePathsString = feedEntity.imagePaths {
let imagePathsArray = imagePathsString.components(separatedBy: ",")
storageManager.deleteImages(from: imagePathsArray)
}
// Core Data에서 엔티티 삭제
context.delete(feedEntity)
try context.save()
print("FeedItem이 성공적으로 삭제되었습니다.")
}
} catch {
print("FeedItem 삭제 실패: \(error)")
}
}
🔥 request.predicate = NSPredicate(format: "feedID == %@", feedID) 이게 무슨 뜻이야?
NSPredicate(format: "feedID == %@", feedID)는 Core Data의 NSFetchRequest에 조건을 설정해주는 구문입니다. 이 구문은 요청된 데이터 중 feedID 속성이 특정 feedID 값과 일치하는 항목만 필터링하여 가져오도록 설정합니다.
- NSPredicate는 조건을 설정하기 위한 객체로, Core Data에서 데이터를 가져올 때나 필터링할 때 사용합니다.
- format: "feedID == %@"는 "feedID" 속성이 지정된 값 (feedID)과 같을 때만 데이터를 가져오라는 의미입니다. 여기서 ==는 비교 연산자로 사용됩니다.
- %@는 서식 지정자(플레이스홀더)로, 뒤에 이어지는 feedID 파라미터 값이 이 자리에 들어가게 됩니다. 즉, feedID 값이 일치하는 데이터만 요청하게 됩니다.
예를 들어, feedID가 "1234-5678-ABCD"인 데이터를 삭제하려고 할 때, 아래와 같이 조건을 설정합니다:
let feedIDToDelete = "1234-5678-ABCD"
let request: NSFetchRequest<FeedEntity> = FeedEntity.fetchRequest()
request.predicate = NSPredicate(format: "feedID == %@", feedIDToDelete)
feedID가 "1234-5678-ABCD"인 데이터만 가져오거나 삭제 대상으로 선택됩니다.
2. 테이블뷰에 스와이프 삭제 액션 추가
UITableViewDataSource 프로토콜의 tableView(_:commit:forRowAt:) 메서드를 사용해 삭제 기능을 구현할 수 있습니다.
extension ProfileViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return feedItems.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: FeedTableCell.identifier, for: indexPath) as? FeedTableCell else { return UITableViewCell() }
let feedItem = feedItems[indexPath.row]
cell.configure(with: feedItem) // FeedTableCell에 FeedItem 전달
return cell
}
// 스와이프하여 삭제
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
// Core Data에서 삭제
let feedItemToDelete = feedItems[indexPath.row]
let feedID = feedItemToDelete.feedID
print("삭제할 FeedItem의 ID: \(feedID)") // 삭제할 feedID 출력
FeedDataManager.shared.deleteFeedItem(feedID: feedID)
// 배열 및 테이블뷰에서 삭제
feedItems.remove(at: indexPath.row)
print(feedItems)
tableView.deleteRows(at: [indexPath], with: .automatic)
}
}
}
'iOS > UIKIT' 카테고리의 다른 글
UISheetPresentation을 통해 수정, 삭제, 닫기 버튼 기능 구현 (0) | 2024.11.16 |
---|---|
FileManager 사용하는 기본 방법 (이미지를 경로로 저장하여 코어 데이터에 가져다 사용하기) (0) | 2024.11.13 |
iOS 앱에서 네트워크 통신을 하는 방법에는 어떤 것들이 있나요? (5) | 2024.11.11 |
UIImage와 UIImageView의 차이 (1) | 2024.11.10 |
갤러리에서 선택한 내용을 컬렉션 뷰에 보이는 방법 (0) | 2024.11.08 |