---
# System prepended metadata

title: 일기장 프로젝트 PR

---

# 일기장 프로젝트 PR

Users/hyejeong/

Library/Developer/CoreSimulator/Devices/A650CECD-7986-4892-9D43-2633A4D30182/data/Containers/Data/Application

![](https://i.imgur.com/CK8opv1.png)

api key : 3a1457f28563a335c26ab4b987303134

call 주소 : https://api.openweathermap.org/data/2.5/weather?lat={lat}&lon={lon}&appid={API key}

"weather": [
    {
      "id": 501,
      "main": "Rain",
      "description": "moderate rain",
      "icon": "10d"
    }
  ]
  
이미지 : https://openweathermap.org/img/wn/10d@2x.png

---
@1Consumption

안녕하세요 올라프! 코낄이, 혜모리 인사드립니다. 🙇🏻‍♂️🙇🏻‍♀️
3주간 리뷰 잘 부탁드리겠습니다. :smile: 

## 고민했던 점

### 1️⃣ 키보드가 편집중인 텍스트를 가리지 않도록 처리
키보드가 나타날 때 편집중인 텍스트를 가리지 않도록 하기 위해 두 가지 방법을 찾아 고려하였습니다.
- 키보드가 나타날 때 `textView`의 오토레이아웃을 변경하는 방법으로 구현
- 키보드가 나타날 때 `textView.contentInset`을 변경하는 방법으로 구현

두 방법 모두 Notification을 활용하여 구현하는 방법을 고려하였고 약간의 딜레이가 느껴졌습니다.
다만 오토레이아웃을 변경하는 방법으로 구현할 경우, 다음과 같이 딜레이 시간동안 키보드가 내려감에도 채워지지 않는 레이아웃이 어색하게 느껴졌습니다.

<img src="https://i.imgur.com/WKsu9Hb.gif" width=200>

<br>

따라서 상대적으로 자연스럽다고 생각되는 `textView.contentInset`을 변경하는 방법으로 구현하였습니다.

### 2️⃣ Attributed String 사용

각 Table View Cell에 2번째 줄에는 작성일자와 내용이 들어가는데 예시로 제공된 화면에서 날짜와 내용의 폰트 크기가 달랐습니다.
따로 두 개의 Label을 둘지, Attributed String을 사용하여 하나의 Label에서 폰트 크기를 다르게 줄지 고민해 보았습니다.
프로젝트 부가 경험에 포함되어있고 Contents의 데이터를 단순히 보여주기만 하는 역할을 하고 있어 하나의 Label에서 보여주는 것으로 결정하였습니다.

``` swift
fileprivate extension UILabel {
    func applyAttribute(targetString: String) {
        guard let fullText = text else { return }
        
        let attributedString = NSMutableAttributedString(string: fullText)
        let range = (fullText as NSString).range(of: targetString)

        attributedString.addAttribute(.font,
                                      value: UIFont.preferredFont(forTextStyle: .body),
                                      range: range)
        attributedText = attributedString
    }
}
```

UILabel의 extension으로 구현해 보았는데 우선 해당 부분에만 적용될 기능이라 판단해 `ContentsTableViewCell` 파일에 두었습니다.

### 3️⃣ 작성일자 형식 변경

사용자의 언어에 따라 작성일자의 형식이 다르게 보이도록 적용해 보았습니다.
아이폰 단말기 설정의 선호하는 언어를 `.preferredLanguages`로 불러올 수 있었습니다.

``` swift
extension Date {
    func translateLocalizedFormat() -> String {
        let formatter = DateFormatter()
        
        formatter.locale = Locale(identifier: Locale.preferredLanguages.first ?? Locale.current.identifier)
        formatter.dateStyle = .long
        
        return formatter.string(from: self)
    }
}
```

---
@1Consumption
안녕하세요 올라프! 코낄이, 혜모리입니다. 🙇🏻‍♂️🙇🏻‍♀️
코어 데이터를 이용하여 사용자의 데이터를 다루는 것이 처음이라 많이 어려웠습니다.
예외 처리를 위해 많은 고민을 하였습니다. 
이번 리뷰도 잘 부탁드리겠습니다. :smile: 

## 고민했던 점

### 1️⃣ core data 주요 프로퍼티, 메서드

- `var persistentContainer` : 데이터가 저장될 컨테이너로 core data가 필요하지 않을 경우 로드할 필요가 없으므로 lazy var로 선언해 주었습니다.
- `var context` : view context 프로퍼티입니다.
- `func create(contents: Contents)` : 데이터를 생성해주는 메서드입니다. 모델 타입 identifier(UUID) 값은 DiaryDetailViewController에서 createContents() 호출 시 생성해 줍니다.
- `func read() -> [Contents]?` : 모든 코어 데이터를 읽는 메서드입니다. DiaryListViewController viewDidLoad 시 모든 데이터를 패치해 주기 위해 구현했습니다.
- `func update(contents: Contents)` : 코어데이터의 일기장 글 내용을 수정해주는 메서드입니다.
- `func delete(identifier: UUID)` : 코어데이터의 일기장 글을 삭제해주는 메서드입니다.
- `func searchContents(identifier: UUID) -> [ContentsEntity]` : 데이터 수정, 삭제를 위해 UUID로 해당 데이터를 찾을 수 있는 메서드를 구현하였습니다.

### 2️⃣ 예외 처리 및 협의한 내용

스텝 요구사항 외 처리가 필요하다고 생각하는 정책을 팀원 간 협의하여 구현한 내용입니다.

1. 새 글을 create할 때 아무 내용도 입력하지 않은 경우
 → 저장되지 않습니다.
3. 새 글을 create하고 + 자동 저장된 후 글 내용을 전부 삭제한 경우
 → 비어있는 내용이 저장됩니다.
5. 글을 한 줄만 등록한 경우 (title만)
 → title은 입력한 내용이 저장되고 body는 빈 문자열로 저장됩니다.
7. 이미 등록된 글의 내용을 전부 지운 경우 update 여부
 → 비어있는 내용이 저장됩니다.
9. 새 글을 create할 때 더보기 버튼(right bar button) 노출 여부
 → 입력 후 내용을 바로 공유하거나, 자동저장된 내용을 바로 삭제시킬 수 있다고 생각하기 때문에 새 글을 저장할 때도 버튼이 노출됩니다.

### 3️⃣ delegate로 table view load
일기장 화면(`DiaryDetailViewController`)에서 변경한 내용이 리스트 화면(`DiaryListViewController`)에도 반영될 수 있도록 두 가지 방법을 고려하였습니다.

1. 일기장 화면에서 변경된 데이터를 CoreData에만 반영하고, 리스트 화면에서 viewWillAppear 메서드에서 `CoreDataManager.read()`와 `TableView.reloadData` 실행
이 방법은 구현이 간단하지만, 하나의 데이터만 변경되어도 전체 데이터를 fetch한 후 reload를 하는데 오버헤드가 발생하게 됩니다.
2. 일기장 화면에서 변경된 데이터를 delegate를 통해 리스트 화면에 반영
delegate를 통해 변경된 셀의 데이터만 reload하기 때문에 성능상의 이점이 있다고 판단하고 이 방법을 적용하였습니다.



### 4️⃣ Alert으로 에러 처리 (rootVC 관련)
에러가 발생했을 때 사용자에게 Alert을 표시하도록 구현하였습니다. 이 기능은 `AlertManager`가 수행하는데, Alert을 `present`할 주체에 대해 고민하였습니다.

초기에는 VC에서만 Alert이 발생한다고 생각해서 다음과 같이 `target`을 파라미터로 받도록 구현하였습니다.

**수정 전: VC에서만 showErrorAlert 호출**
``` swift
struct AlertManager {
    func showErrorAlert(target: UIViewController, error: Error) {
        let alert = UIAlertController(...)
        ...
        target.present(alert, animated: true)
    }
    ...
}
```
하지만 `CoreDataManager`에서 에러가 발생할 경우 VC에 대한 정보를 모르는 채로 Alert을 표시할 필요가 있었습니다. 따라서 VC의 정보를 파라미터로 넘겨받지 않고도 Alert을 표시할 수 있도록 다음과 같이 수정하였습니다.

**수정 후: VC가 아닌 모델에서도 showErrorAlert 호출**
``` swift
struct AlertManager {
    func showErrorAlert(error: Error) {
        guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
              let target = windowScene.windows.first?.rootViewController else { return }
        
        let alert = UIAlertController(...)
        ...
        target.present(alert, animated: true)
    }
    ...
}
```
위 코드는 현재 VC가 몇개가 생성되어있든, Root VC에서 present 합니다.
<img src="https://i.imgur.com/8vywbGL.png" width=250>

동작에는 문제가 없는것을 확인했지만, 흐름상 `VC 2`가 존재하는 상황이라면 `VC 2`에서 Alert을 `present` 하는것이 더 자연스럽게 느껴지기도 합니다. 저희 코드에서는 `VC 1`에서 Alert을 `present` 하는데, 이 부분에 대해 올라프의 의견을 여쭙고 싶습니다. 


---
# STEP 3
일기장 [STEP 3] 혜모리, kokkilE

@1Consumption

안녕하세요 올라프! 코낄이, 혜모리입니다. 🙇🏻‍♂️🙇🏻‍♀️
드디어 일기장 마지막 스텝 PR이네요 이번 리뷰도 잘 부탁드리겠습니다 ☺️

### 구현한 기능
- 새로운 일기 작성을 시작할 때 현재 사용자의 위치를 기반으로 현재 날씨정보 가져오기
    - Open API 통신 기능 구현
    - 상세 페이지에서 새로운 일기가 작성될 때에만 위치 정보 요청 및 수집
- 일기 정보에 현재 날씨의 main, iconCode 저장
    - ContentsDTO 모델에 해당 데이터 추가
- 리스트의 날짜와 한줄요약 사이에 날씨 아이콘 표시
    - 상세 화면에서 저장한 날씨 정보로 리스트 화면에 아이콘 표시

---
## 고민했던 점
### 1️⃣ 날씨 표시 기능 구현 전에 저장된 데이터를 보존하기 위한 처리
이번 스텝에 날씨 정보를 표시하는 기능을 추가하면서 Core Data의 Entity 모델이 변경되었고, 그에 따라 기존에 Core Data에 저장되어있던 모델과 Migration하였습니다.

이 과정에서 기존에 날씨 정보가 없던 데이터는 날씨 정보가 없는 채로 받아오기 위해, `ContentsDTO`에 다음과 같이 `weather`를 옵셔널로 구현하였습니다.

``` swift
/*  ContentsDTO은 다음의 용도로 사용됩니다.
    1. JSON 데이터 Decode를 위한 모델
    2. VC에서 사용되는 모델
    3. VC에서 CoreData와 데이터를 주고받는 모델 */

struct ContentsDTO: Codable {
    var title: String
    var body: String
    let date: Double
    let identifier: UUID?
    var weather: Weather?
    ...
}
```

그 결과 날짜 정보가 없으면 없는 대로, 있으면 있는 대로 앱에서 다음과 같이 표시됩니다.

<img src="https://hackmd.io/_uploads/S1IcfYdNh.gif" width=220>

### 2️⃣ 날씨 아이콘을 fetch해오는 시점

날씨 아이콘 코드를 받기위해 api 통신 시점을 고민했습니다.
테이블 뷰가 있는 `DiaryListViewController`에서 하기보다는,
글을 최초로 등록하는 `DiaryDetailViewController`에서 아이콘 코드를 받아 재활용할 수 있도록 Core Data에 저장하였습니다.
`DiaryListViewController` 테이블 뷰 셀에서 아이콘 코드를 이용해 이미지를 받아올 수 있도록 구현하였습니다.

---
## 조언이 필요한 점
프로젝트의 폴더 구조에 대한 고민도 많았습니다.
다른 Github 레포지토리를 참고해봐도 각자 스타일이 다른 것 같았는데요, 저희는 폴더 depth가 깊은 것 보다는 얕은것이 직관적으로 보인다고 생각했습니다.
저희 프로젝트의 폴더 구조를 봤을 때 잘 이해가 가질 않는다거나, 보시기에 불편했던 점이 있으셨다면 조언해주시면 감사드리겠습니다. 🙇🏻‍♂️🙇🏻‍♀️