신규 작성(create) 모드에서는 화면에 들어오자마자 “감정 선택 BottomSheet”를 자동으로 띄우고,
수정(edit) 모드에서는 기존 감정을 유지하므로 자동으로 띄우지 않는다.
🔥 왜 이 기능이 필요한가?
감정일기를 작성할 때 사용자의 실제 흐름은:
1. 일기를 작성하러 들어옴 →
2. 가장 먼저 감정을 선택 →
3. 그 아래 내용들을 채움
그래서 신규 작성 화면에서는 자동으로 감정 선택창을 띄우면 UX가 훨씬 자연스럽다.
반면 수정 모드에서는 이미 선택된 감정이 있으므로 자동으로 띄우면 흐름이 오히려 깨진다.
📚 1. 구현 개요
아래 로직을 기준으로 설계했다:
1. DiaryEditorViewModel.mode가 .create일 때만 자동 오픈
2. 이미 감정이 선택돼 있으면 오픈 안 함
3. 셀이 아직 렌더링되지 않았을 수 있으므로 안전하게 DispatchQueue로 체크
4. EmotionCell 내부에서 감정 선택 BottomSheet를
여는 메서드(triggerOpenEmotionPicker)를 호출
🧩 2. openEmotionSelectorIfNeeded() 실제 코드
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
openEmotionSelectorIfNeeded()
}
// MARK: 자동 감정 선택창 오픈
private func openEmotionSelectorIfNeeded() {
// 📌 edit 모드는 자동으로 열지 않음
if case .edit = diaryEditorVM.mode {
return
}
// 📌 이미 감정을 선택한 경우도 열지 않음
if !diaryEditorVM.diary.emotion.isEmpty { return }
let indexPath = IndexPath(item: 0, section: DiaryEditorSection.emotion.rawValue)
guard let cell = diaryCollectionView.cellForItem(at: indexPath) as? EmotionCell else {
// 셀이 아직 로딩되지 않았으면 0.05초 후 재시도
DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) {
self.openEmotionSelectorIfNeeded()
}
return
}
// 📌 EmotionCell 내부의 Picker 오픈 함수 호출
cell.triggerOpenEmotionPicker()
}
✨ 3. EmotionCell 내부에서 호출할 trigger 메서드
EmotionCell은 자신의 역할을 유지해야 한다.
그래서 아래 메서드를 만들어서 VC가 호출할 수 있게 했다:
func triggerOpenEmotionPicker() {
onAddButtonTapped?()
}
즉,
“뷰컨트롤러가 직접 present하는 게 아니라”
감정 버튼을 눌렀을 때와 동일한 흐름을 타도록 구현했다.
➡️ UIKit에서 Cell이 직접 화면을 띄우지 않는다는 규칙 유지
➡️ VC → Cell → VC로 흐름이 자연스러움
📌 4. 전체 흐름 요약
| 모드 | 자동 오픈 여부 | 이유 |
| Create | 자동 오픈 | 새 일기는 감정 선택부터 시작하는 UX가 자연스러움 |
| Edit | 자동 오픈 ❌ | 이미 감정이 선택된 상태이므로 불필요 |
📌 저장 완료 후 화면 닫기 처리 (MVVM 패턴 준수)
이 기능은 매우 중요하다.
ViewController가 직접 “저장 성공 → dismiss()” 처리하는 것이 아니라,
ViewModel이 저장 성공 이벤트를 발행하고
ViewController가 그걸 구독해 처리해야 MVVM 구조가 유지된다.
🎯 1. 핵심 설계 포인트
- 저장 로직(saveDiary)은 ViewModel에서 실행
- 저장 완료 시, ViewModel이 “저장 완료됨” 이벤트 발행
- ViewController는 Combine으로 이 이벤트를 구독
- 저장 성공 시 화면 dismiss 처리
이렇게 하면:
- VC는 “저장 진행 상황”이나 “DataStore와 통신”에 전혀 관여하지 않음
- ViewModel은 “화면을 닫는다” 같은 UI 로직을 몰라도 됨
→ 역할이 완벽히 분리된 MVVM 구조
🧩 2. ViewModel에서 저장 완료 이벤트 발행
ViewModel 내부 코드:
@Published var saveCompleted: Bool = false
저장 성공 시:
private func saveDiary() {
store.save(diary)
saveCompleted = true
}
🧩 3. ViewController에서 이벤트 구독 후 dismiss 처리
private func bindViewModel() {
// 유효성 검사 결과 구독 -> 참일 경우 창 닫힘
diaryEditorVM.$saveCompleted
.receive(on: RunLoop.main)
.sink { [weak self] completed in
guard let self, completed else { return }
self.navigationController?.dismiss(animated: true)
}
.store(in: &cancellables)
}
4. 전체 저장 흐름 요약
사용자 → 저장 버튼 클릭
VC → ViewModel의 attemptSaveDiary 호출
ViewModel에서 입력값 유효성 검사
성공이면 saveDiary() 실행
ViewModel이 saveCompleted = true를 publish
VC가 이를 구독해서 화면 dismiss
➡️ VC는 저장 로직에 전혀 관여하지 않음 (MVVM 유지)
➡️ 사용자는 저장이 완료되면 자연스럽게 화면이 닫힌다
'감정일기(가칭)' 카테고리의 다른 글
| ✅ 주간 (일 ~ 토) 날짜를 요일 enum으로 매핑해주는 함수 (0) | 2025.11.21 |
|---|---|
| Popover 위에 “어두운 배경(딤)” 깔고 싶다 (0) | 2025.11.19 |
| 📑 DiaryEditor 유효성 검사 설계부터 UI 처리까지 (0) | 2025.11.18 |
| 📘 DiaryContentCell 리팩토링 기록 — 단일 String 통합 방식에서 → 구조체 기반 다중 필드 관리로 개선하기 (0) | 2025.11.17 |
| 📌 iOS 파일 선택(UIDocumentPicker) 시 이미지가 안 보이던 문제 해결하기 (0) | 2025.11.15 |