본문 바로가기
Project/ReceiptMind

📚 iOS MVVM 가계부 앱 - Core Data 수정(Update) + 이미지까지 깔끔히 반영하는 법

by 밤새는 탐험가89 2025. 8. 2.
728x90
SMALL
가계부 앱을 만들면서 생기는 대표적인 고민 중 하나는 "기존에 등록된 내역을 수정할 때 이미지나 메모를 어떻게 반영하지?" 입니다.
이번 글에서는 Core Data에서 기존 내역을 업데이트하고, 선택적으로 이미지까지 덮어쓸 수 있는 구조를 소개합니다.

💡 목표 기능

  • 사용자가 등록한 기존 내역(지출/수입)을 수정
  • 메모, 금액, 카테고리 등 변경 사항 반영
  • 기존 이미지가 있을 경우:
    • 이미지 삭제
    • 이미지 교체
  • 기존에 이미지가 없더라도 새로 추가 가능

📦 업데이트 함수 코드

func updateTransaction(_ updatedTransaction: ExpenseModel) -> AnyPublisher<ExpenseModel, Error> {
    return Future { [weak self] promise in
        guard let self = self else {
            print("❌ TransactionManager: self가 nil")
            return
        }

        let fetchRequest: NSFetchRequest<ExpenseEntity> = ExpenseEntity.fetchRequest()
        fetchRequest.predicate = NSPredicate(format: "id == %@", updatedTransaction.id.uuidString)
        fetchRequest.fetchLimit = 1

        do {
            guard let entity = try self.context.fetch(fetchRequest).first else {
                print("❌ 업데이트할 항목 없음")
                promise(.failure(NSError(domain: "TransactionUpdateError", code: 404)))
                return
            }

            // 기본 필드 업데이트
            entity.transaction = updatedTransaction.transaction.rawValue
            entity.amount = Int64(updatedTransaction.amount)
            entity.date = updatedTransaction.date
            entity.category = updatedTransaction.category
            entity.memo = updatedTransaction.memo

            // 이미지 처리
            if let newImage = updatedTransaction.image {
                // 기존 이미지가 있을 경우 제거
                if let oldPath = entity.imagePath, !oldPath.isEmpty {
                    let deleted = self.storageManager.deleteFolder(for: oldPath)
                    if !deleted {
                        print("⚠️ 기존 이미지 삭제 실패: \(oldPath)")
                    }
                }

                // 새 이미지 저장
                if let newPath = self.storageManager.saveImage(newImage, updatedTransaction.id.uuidString) {
                    entity.imagePath = newPath
                } else {
                    print("❌ 새 이미지 저장 실패")
                    promise(.failure(NSError(domain: "ImageSaveFailed", code: 500)))
                    return
                }

            } else {
                // 새 이미지가 없는 경우
                // 기존 이미지 삭제 대상일 수도 있음
                if let oldPath = entity.imagePath, !oldPath.isEmpty {
                    let deleted = self.storageManager.deleteFolder(for: oldPath)
                    if deleted {
                        entity.imagePath = nil
                        print("🗑️ 기존 이미지 삭제 완료")
                    } else {
                        print("⚠️ 기존 이미지 삭제 실패")
                    }
                } else {
                    print("ℹ️ 삭제할 이미지 없음")
                }
            }

            try self.context.save()
            print("✅ 업데이트 성공!")
            promise(.success(updatedTransaction))

        } catch {
            print("❌ Core Data 업데이트 실패: \(error.localizedDescription)")
            promise(.failure(error))
        }
    }
    .eraseToAnyPublisher()
}

🧭 함수 흐름 설명: updateTransaction(_:) 은 어떻게 작동하는가?

🔢 1단계: 기존 데이터 탐색

let fetchRequest: NSFetchRequest<ExpenseEntity> = ExpenseEntity.fetchRequest()
fetchRequest.predicate = NSPredicate(format: "id == %@", updatedTransaction.id.uuidString)
fetchRequest.fetchLimit = 1

 

  • 무엇을 하는가?
    Core Data 안에서, 수정하고자 하는 UUID와 동일한 ExpenseEntity 하나를 찾기 위해 FetchRequest를 생성합니다.
  • 왜 중요한가?
    수정은 "기존 데이터"가 존재할 때만 의미가 있기 때문입니다. 없는 데이터를 수정하려 하면 실패 처리해야 하죠

🔁 2단계: 기본 필드 수정

entity.transaction = updatedTransaction.transaction.rawValue
entity.amount = Int64(updatedTransaction.amount)
entity.date = updatedTransaction.date
entity.category = updatedTransaction.category
entity.memo = updatedTransaction.memo

 

 

  • 무엇을 하는가?
    사용자로부터 전달받은 최신 데이터(ExpenseModel)를 Core Data의 Entity에 반영합니다.
  • 포인트
    이 단계는 이미지와 관계없이, 금액, 카테고리, 날짜, 메모 등의 "텍스트 기반 정보"만을 업데이트합니다.

 

🖼️ 3단계: 이미지 처리 (조건 분기)

if let newImage = updatedTransaction.image {
    // 기존 이미지 삭제 → 새 이미지 저장
} else {
    // 기존 이미지만 삭제 (새 이미지 없음)
}
  • 무엇을 하는가?
    사용자가 이미지를 변경하거나 삭제했는지 여부에 따라 처리 방식을 나눕니다.

💾 4단계: 변경 내용 저장

try self.context.save()

 

  • 무엇을 하는가?
    위에서 변경한 필드와 이미지 경로 정보를 Core Data에 실제로 반영합니다.
  • 에러 발생 시 사용자에게 실패 메시지를 반환합니다.

✅ 5단계: 최종 성공 반환 

promise(.success(updatedTransaction))

 

 

  • 무엇을 하는가?
    모든 과정이 성공적으로 완료되면, 변경된 모델을 Combine 구독자에게 전달합니다.
  • 왜 필요한가?
    UI에서는 이 성공 결과를 받아서 화면을 갱신하거나, 알림을 줄 수 있어야 하기 때문입니다.

🔁 요약 플로우 차트

📍 사용자로부터 수정된 ExpenseModel 수신
     ↓
1️⃣ ID 기반으로 기존 ExpenseEntity 조회
     ↓
2️⃣ 텍스트 기반 필드(transaction, amount, date 등) 수정
     ↓
3️⃣ 이미지 처리 (삭제/교체/유지 여부에 따라 분기)
     ↓
4️⃣ context.save()로 Core Data 저장
     ↓
5️⃣ 성공 시 → 업데이트된 모델 반환

 


🔍 로직 정리 (4가지 이미지 처리 상황)

기존 이미지 새 이미지 동작 요약
✅ 있음 ✅ 있음 기존 이미지 삭제 → 새 이미지 저장
✅ 있음 ❌ 없음 기존 이미지 삭제
❌ 없음 ✅ 있음 새 이미지 저장
없음 ❌ 없음 이미지 관련 작업 없음

 


 

 

[함께 보면 좋은 글]

https://explorer89.tistory.com/444

 

📚Core Data로 지출 내역 불러오기(Read) 정리

✨ 목표이번 글에서는 가계부 앱에서 Core Data로 저장된 지출 내역을 전체 조회하거나 특정 ID로 조회하는 방법과, Core Data Entity(ExpenseEntity)를 우리가 ViewModel/화면에서 사용하기 위한 데이터 모델(E

explorer89.tistory.com

 

728x90
LIST