# 일기장
안녕하세요 엘림 @lina0322
일기장 step2 구현이 완료되어 PR을 보냅니다.
## 📝 STEP 진행 중 경험하고 배운 것
- CoreData 적용
☑️ CoreData를 활용해 CRUD를 구현하기
- NotificationCenter 활용하기
☑️ 키보드가 나타나고 사라질 때 키보드의 위치 정보 구하기
☑️ 앱이 인액티브 상태가 되었을 때 알리기
- UIGestureRecognizerDelegate 활용하기
☑️ Gesture를 이용하여 아래로 스와이프 중 키보드에 닿았을 때 키보드를 사라지게 하기
# 구현 화면
### Localizable
| 한국어 설정 | 영어 설정 |
|:--:|:--:|
|||
# 고민했던 점
- NSPersistentContainer의 위치와 활용방법
- 처음 `NSPersistentContainer`의 위치는 프로젝트를 만들 때 `AppDelegate`내부에 자동으로 생성됩니다. 하지만 이 위치에 있을 때 다른 곳에서 CoreData를 사용해야 할 때 매번 `AppDelegate`를 다운캐스팅해서 사용해야만 했습니다. 매번 옵셔널 바인딩과 CoreData를 사용하면서 에러를 핸들링 하는 과정들이 코드에서 불필요하게 반복된다 생각해 이 컨테이너를 가지고 있고 CoreData관련 로직들을 처리하는 하나의 커스텀 객체를 만드는게 더 좋다 판단하였습니다. 그래서 `DiaryDataStore` 타입을 만들어 `NSPersistentContainer`와 `context`를 가지게 하고, 이 타입을 통해 CoreData를 관리하도록 로직을 짜 보았습니다.
- `SceneDelegate`의 `sceneDidEnterBackground`메서드에서 불리우던 `saveContext` 메서드의 경우, 메모장의 입력화면에서 `UIScene.willDeactivateNotification`을 옵저빙하도록 하여 이 노티가 불릴 때 마다 `saveContext`를 하는 방향으로 해결해 보았습니다.
- CoreData의 시점에 관한 고민
- coreData에 object를 생성하고 context.save()를 한 뒤 사용해야 했습니다. save를 하지 않고 사용하게 된다면 object는 실제 저장된 ID가 아닌 임시ID로 사용하게 되어 update나 delete시 오류가 발생할 수 있습니다
# 조언을 얻고 싶은 점
- CoreData를 사용하는 방법
- update, delete작업을 할 때 **objectID를 통해 container에 있는 object를 직접 가져와서 업데이트, 삭제를 해주는 방식**을 사용했습니다.
step2를 구현한 후에 얘기를 나누다가 프로젝트를 생성할 때 coredata를 체크하고 만들면 sceneDelegate에 saveContext()가 들어가는 것이 생각났습니다
앱이 백그라운드로 들어갈 때 **변경사항이 있으면 저장**을 한다는 것 때문인 것으로 보였는데 이게 **github의 원격저장소, 로컬저장소의 개념과 비슷**해보였습니다.
로컬저장소에서 생성, 수정, 삭제 등을 실행하고 원격저장소에 push를 해주듯이 앱에서 object를 만들고 업데이트하고 삭제하는 등의 작업을 하고 **백그라운드에 들어가거나 앱이 종료할 때에만 container에 saveContext를 해주기만 하면 되는게 아닌가?** 하는 생각이 들었습니다.
구현이 끝난 후에 생각이 들었던 지라 직접 해보지는 못한 상태인데혹시 위처럼 생각한 것이 가능한지 여부을 듣고 싶습니다.
<!-- - Container를 관리하는 객체인 DiaryDataStore를 만들어주었습니다. -->
<!--
안녕하세요 엘림 @lina0322
일기장 step2 구현이 완료되어 PR을 보냅니다.
## 📝 STEP 진행 중 경험하고 배운 것
- SwiftLint 적용
☑️ SwiftLint이용해 코드 컨벤션 지키기
- Date Formatter 활용하기
☑️ DateFormatter를 활용해 사용자의 지역 포맷에 맞게 날짜 출력하기
- NotificationCenter 활용하기
☑️ 키보드가 나타나고 사라질 때 키보드의 위치 정보 구하기
# 구현 화면
### 설정 언어에 따른 날짜 포맷 설정
| 한국어 설정 | 영어 설정 |
|:--:|:--:|
|||
### 실행화면
| 실행 화면 |
|:--:|
|<img width = 300, src= "https://i.imgur.com/d8ouotJ.gif">|
# 고민했던 점
### pod을 사용하며 podfile을 ignore
podfile을 올린 이후 gitignore로 Pod폴더를 적용했습니다.
하지만 git에 먼저 podfile이 올라갔기 때문에 pod install을 하지 않아도 사용할 수 있을 것이라 생각했습니다.
왜 안되는지 고민을 한 결과 pod install을 해주어야 pod을 다운받아서 라이브러리가 적용되는 것을 확인했습니다.
# 조언을 얻고 싶은 점
### delegate와 dataSource는 누가 해야하나?
- 이전 오픈마켓을 진행하면서 매우 방대해진 ViewController를 경험하면서, 각 뷰 컨트롤러에 맞는 커스텀 UIView 객체를 만들어 뷰컨트롤러의 view프로퍼티를 커스텀 UIView로 교체하는 등의 로직을 구현해 보았습니다.
```swift
final class DiaryView: UIView {
//뷰 컴포넌트 구현
}
final class DiaryViewController: UIViewController {
override func loadView() {
super.loadView()
self.view = diaryView
}
}
```
- 하지만, tableView나 collectionView와 같은 객체들의 delegate와 dataSource를 뷰 컨트롤러가 하도록 하다보니 다시 뷰 컨트롤러가 방대해졌습니다.
- 그래서 이것도 위 방법과 비슷하게 `UITableViewDelegate`객체와 `UITableViewDataSource`객체를 커스텀으로 직접 만들어 만든 객체가 관련된 로직을 수행하고, 이를 뷰 컨트롤러가 소유하게 하는 방법을 생각했습니다.
```swift
final class DiaryTableViewDelegate: UITableViewDelegate {
// 델리게이트 기능 구현
}
final class DiaryTableViewDataSource: UITableViewDataSource {
// 데이터소스 기능 구현
}
final class DiaryViewController: UIViewController {
private let tableView: UITableView = UITableView()
private let delegate: UITableViewDelegate = DiaryTableViewDelegate()
private let dataSource: UITableViewDataSource = DiaryTableViewDataSource()
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = delegate
tableView.dataSource = dataSource
}
}
```
- 먼저, dataSource를 객체로 만들었을 때 데이터소스 객체가 모델을 가지고 있고, 그를 활용해서 테이블 뷰에 데이터를 뿌려주는 느낌이었습니다. 이는 뷰 컨트롤러가 하는 일이 아닌가? 라는 의문이 들었습니다.
```swift
class TestDataSource: NSObject, UITableViewDataSource {
private var diaryContents: [DiaryContent] = []
init(data: [DiaryContent]) {
diaryContents = data
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return diaryContents.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: DiaryListCell.identifier) as? DiaryListCell
else {
return UITableViewCell()
}
cell.setupLabelText(from: diaryContents[indexPath.row])
return cell
}
}
```
- 또, delegate를 객체로 만들었을 때 delegate가 다음 화면을 띄우기 위해 네비게이션 컨트롤러를 가지고 있게 되고 화면을 띄우기 전에 뷰 컨트롤러에 모델에 대한 데이터를 넘겨주기 위해 모델을 소유하게 되는 등 뭔가.. 찜찜한 객체가 만들어 졌습니다.
```swift
class TestTableViewDelegate: NSObject, UITableViewDelegate {
let navigationController: UINavigationController
private var diaryContents: [DiaryContent] = []
init(data: [DiaryContent], navi: UINavigationController) {
diaryContents = data
navigationController = navi
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
let editorViewController = EditorViewController()
editorViewController.configureEditorView(from: diaryContents[indexPath.row])
self.navigationController.pushViewController(editorViewController, animated: true)
}
}
```
- 결과적으로 데이터소스와 델리게이트 역할을 하는 컨트롤러 객체만 더 생겼을 뿐이고, 방대한 컨트롤러를 소스파일로 나누기만 한 느낌이 들어 이를 적용하지 않았습니다. 저희가 생각한 이 방법과 방향성에서 조언주실 부분이나 제시해주실 부분이 있을까요? -->