🔹 목표
독서 계획(ReadItem) 안에 여러 개의 독서 메모(ReadMemo)를 추가하고 관리하는 기능을 CoreData의 관계(Relationship) 를 활용하여 저장하고, Combine + MVVM 패턴으로 CRUD 기능을 구현한다.
🔹 1. CoreData에 ReadItem ↔ ReadMemo 관계 설정
✅ ReadItem (독서 계획)
- id: UUID
- title: String
- startDate: Date
- endDate: Date
- memos: ReadMemo와 1:N 관계 (📌 중요!)
✅ ReadMemo (독서 메모)
- id: UUID
- parentID: UUID (ReadItem과 연결)
- memo: String
- page: Int
- readItem: ReadItem과 관계 설정 (📌 중요!)
💡 CoreData 관계 설정 방법
- ReadItem ↔ ReadMemo 사이 1:N 관계 설정
- ReadItem이 여러 개의 ReadMemo를 가질 수 있도록 설정
- ReadMemo의 readItem 속성을 Optional X, Delete Rule = Cascade
- 부모 ReadItem이 삭제되면, 관련된 ReadMemo도 자동 삭제
CoreData 모델을 설정한 후, NSManagedObject 클래스를 생성해야 함!
(Editor > Create NSManagedObject Subclass 사용)
🔹 2. 설계 요약 흐름
- DetailViewController → AddMemoViewController로 ReadItemModel을 전달
- AddMemoViewController가 AddMemoViewModel에 ReadItemModel을 전달
- AddMemoViewModel에서 ReadItemModel이 설정되면 자동으로 CoreData의 ReadItem으로 변환
- 변환된 ReadItem을 selectedReadItem에 저장
- 저장 버튼 누르면 selectedReadItem을 이용해 CoreData에 저장
👉 결론: ReadItemModel을 ReadItem으로 변환하는 과정이 자동으로 실행되도록 하고 싶음
🔹 3. 설계 시작~
// ✅ ReadMemoModel: 독서 메모 클래스
class ReadMemoModel {
var id: String // ex) "readItemID_memoID"
var memo: String
var page: Int
init(parentID: UUID, memo: String, page: Int) {
self.id = "\(parentID.uuidString)_\(UUID().uuidString)"
self.memo = memo
self.page = page
}
}
📍 DetailViewController 에서 AddMemoViewController로 화면 전환할 때 ReadItemModel 타입의 readItem(독서계획)을 전달한다.
이러면 이 데이터를 AddMemoViewModel 클래스 내에 readItemModel 프로퍼티에 전달한다.
그러면 프로퍼티 감시자를 통해 fetchReadItem() 메서드를 실행한다.
이를 통해 ReadItemModel 타입의 데이터를 코어데이터의 엔티티 타입에 대한 ReadItem 타입으로 반환한다.
이 반환된 데이터를 selectedReadItem에 할당한다.
이를 갖고, createReadMemo 메서드에서 독서 메모의 id를 설정할 때 사용할 수 있게 한다.
// ✅ AddMemoViewModel
class AddMemoViewModel {
@Published var readItemModel: ReadItemModel? {
didSet {
fetchReadItem() // 값이 바뀔 때마다 자동 변환
}
}
@Published var selectedReadItem: ReadItem? // CoreData의 ReadItem
@Published var newReadMemo: ReadMemoModel = ReadMemoModel(
parentID: UUID(),
memo: "",
page: 0)
@Published var readMemos: [ReadMemoModel] = []
@Published var errorMessage: String?
private var cancellables: Set<AnyCancellable> = []
let coredataManager = CoreDataManager.shared
// MARK: - Function: ReadItemModel 타입의 데이터를 ReadItem (엔티티) 타입으로 변환
func fetchReadItem() {
guard let model = readItemModel else { return }
if let readItem = coredataManager.fetchReadItem(by: model.id) {
self.selectedReadItem = readItem
print("✅ CoreData에서 ReadItem 변환 완료: \(readItem.id ?? UUID())")
} else {
print("❌ CoreData에서 ReadItem을 찾을 수 없습니다.")
}
}
// MARK: - Functions: CRUD
// Create
func createNewReadMemo(_ memo: ReadMemoModel) {
print("🧑💻 AddMemoViewModel: 새로운 독서 메모 저장 요청")
guard let parent = selectedReadItem else {
print("❌ 저장 실패: selectedReadItem이 없습니다.")
return
}
coredataManager.createReadMemo(memo, for: parent)
.sink { completion in
switch completion {
case .finished:
print("✅ AddMemoViewModel: 독서 메모 저장 완료되었습니다.!")
case .failure(let error):
print("❌ AddMemoViewModel: 저장 실패 \(error.localizedDescription)")
self.errorMessage = error.localizedDescription
}
} receiveValue: { [weak self] newMemo in
print("🧑💻 AddMemoViewModel: 저장된 독서 메모 확인")
print(" - ID: \(newMemo.id)")
print(" - Memo: \(newMemo.memo)")
print(" - Page: \(newMemo.page)")
self?.newReadMemo = newMemo
self?.readMemos.append(newMemo)
}
.store(in: &cancellables)
}
📍 readItemModel을 viewModel에 전달하면 자동으로 변환이 이루어집니다.
class AddMemoViewController: UIViewController {
// MARK: - Variables
private var viewModel: AddMemoViewModel = AddMemoViewModel()
private var cancellables: Set<AnyCancellable> = []
private var mode: AddMemoMode = .create
var readItem: ReadItemModel
var readMemo: ReadMemoModel
...
// ✅ MARK: - Init
init(mode: AddMemoMode, readItem: ReadItemModel, readMemo: ReadMemoModel? = nil) {
self.mode = mode
self.readItem = readItem
self.viewModel.readItemModel = readItem
switch mode {
case .create:
self.readMemo = readMemo ?? ReadMemoModel(parentID: readItem.id, memo: "", page: 0)
case .edit:
self.readMemo = readMemo!
}
self.viewModel.newReadMemo = self.readMemo
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
...
// ✅ MARK: - 저장메서드
@objc private func addMemo() {
print("✅ addMemoButton - called ")
switch mode {
case .create:
viewModel.createNewReadMemo(viewModel.newReadMemo)
print("🎊 새로운 메모 저장: \(viewModel.newReadMemo)")
case .edit:
print("😘 수정된 메모 저장: \(viewModel.newReadMemo)")
}
dismiss(animated: true )
}

'Project > 30MinRead' 카테고리의 다른 글
드디어 앱 스토어 출시 완료! (꾸독!) (0) | 2025.04.06 |
---|---|
📅 독서 시간을 완료한 날짜 저장하여 표시하기 (0) | 2025.03.24 |
🎉 타이머가 완료된 시점에 사용자에게 알림을 주기! (0) | 2025.03.23 |
🔥 시작일 ➡️ 종료일 포함하여 날짜 계산! (0) | 2025.03.21 |
❌ 날짜 잘못 선택하면.. 오류창 띄워야 하는데.. (0) | 2025.03.20 |