---
# System prepended metadata

title: Untitled

---

## 일기장
> 프로젝트 기간: 23/08/28 ~ 23/09/15

## 📂 목차
1. [팀원](#1.)
2. [타임 라인](#2.)
3. [시각화구조](#3.)
4. [실행 화면](#4.)
5. [트러블 슈팅](#5.)
6. [팀 회고](#6.)
7. [참고 문서](#7.)


<a id="1."></a>

## 1. 팀원

| Jusbug | yyss99 |
| :--------: | :--------: |
| <Img src = "https://github.com/JusBug/ios-box-office/assets/125210310/549c2726-aa5a-48cc-a39a-7c10d10bdda5" width="200" height="200"> | <Img src = "https://hackmd.io/_uploads/ryHsN0cTn.png"  width="200" height="200"> |
|[Github Profile](https://github.com/JusBug) |[Github Profile](https://github.com/yy-ss99) |
- - -
<a id="2."></a>

## 2. 타임라인

<details>
<summary>타임 라인</summary>
<div markdown="1">
    
### 2023.08.28.(월)
- README 수정
### 2023.08.29.(화)
- DiaryTableViewCell 등록 및 구현
- Sample 데이터 타입 구현 및 JSON 파일 추가
- JSON파일을 Sample 타입으로 디코딩하는 decodeJSON() 구현
- DiaryTableViewCell 레이아웃 수정
- DateFormatter로 createdDate 날짜형식변환
### 2023.08.30.(수)
- 일기장 생성 버튼 구현
- TextView 생성 및 placeholder 구현
- DetailViewController 네비게이션 타이틀 오늘 날짜 추가
- DetailVC 생성 및 테이블 뷰 커스텀 이니셜라이져로 데이터를 전달하여 didSelectRowAt() 구현
- DetailViewController 편집 시 키보드가 글자 가리는 이슈 
### 2023.09.01 (금)
- final 키워드 명시, 은닉화 처리, 불필요한 프로퍼티 삭제
- NewDiaryViewController 삭제 및 DetailViewController 수정
- 불필요한 주석 제거
- README 업데이트
### 2023.09.14 (목)
- CoreDataManager 생성 및 저장, 수정 메서드 구현
- textView를 하나로 통합
- 새로운 Diary 저장기능 구현
- 추상화 및 조건문 수정, 공유와 삭제가 가능한 didTapMenu() 구현
- 화면 다크모드 적용 및 textColor 변경
- AlertAtion에서 delete 작업 구현
- CoreData에 날짜 저장 기능 추가

</div>
</details>
<a id="3."></a>

## 3. 시각화 구조

### 📐 Diagram

![](https://hackmd.io/_uploads/BJTyDdZka.png)

### 🌲 File Tree
<details>
<summary>File Tree</summary>
<div markdown="1">

```
.
├── Diary
│   ├── Entity+CoreDataClass.swift
│   ├── Entity+CoreDataProperties.swift
│   ├── Application
│   │   ├── AppDelegate.swift
│   │   └── SceneDelegate.swift
│   ├── ViewController
│   │   ├── MainViewController.swift
│   │   ├── DetailViewController.swift
│   │   └── DiaryTableViewCell.swift
│   ├── Model
│   │   ├── Sample.swift
│   │   ├── CustomDateFormatter.swift
│   │   └── CoreDataManager.swift
│   ├── Resources
│   │   ├── Assets
│   │   ├── Diary
│   │   └── Info.plist
│   └── View
│       ├── Base.lproj
│       │   ├── LaunchScreen.storyboard
│       │   └── Main.storyboard
│       └── DiaryTableViewCell.xib
│       
├── README.md
└── sample.json
```

</div>
</details>

</br>
<a id="4."></a>

## 4. 실행 화면
| Create | SwipeDelete | AlertDelete |
| :--------: | :--------: | :--------: |
|<Img src = "https://hackmd.io/_uploads/BkmegKW16.gif" width="200" height="400">|<Img src = "https://hackmd.io/_uploads/HJs43ub1a.gif" width="200" height="400">|<Img src = "https://hackmd.io/_uploads/BJVskY-Jp.gif" width="200" height="400">|

| Update | Share | Date |
| :--------: | :--------: | :--------: |
|<Img src = "https://hackmd.io/_uploads/Hy-I-KZya.gif" width="200" height="400">|<Img src = "https://hackmd.io/_uploads/rJRKkFZJp.gif" width="200" height="400">|<Img src = "https://hackmd.io/_uploads/S1MZVF-1p.gif" width="200" height="400">|

- - -
</br>
<a id="5."></a>

## 5. 트러블 슈팅

### 1. <키보드 가림 이슈>

🤯 **문제상황**
`textField`에 쓰는 글이 길어지다 보면 키보드에 의해 가려지는 현상이 발생했습니다. 

🔥 **해결방법**
키보드의 높이 만큼 `textView`의 `bottomInset`을 올라가도록 만들었습니다. 그래서 글이 쓰여지는 동안에 키보드에 의해 가려지 않고 화면에 보여지게 만들었습니다.

```Swift
@objc func keyboardWillShow(_ sender: Notification) {
    if originalFrame == nil {
        originalFrame = view.frame
    }
        
    if let keyboardFrame = (sender.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue {
        let keyboardHeight = keyboardFrame.height
        let safeAreaBottom = view.safeAreaInsets.bottom
        let contentInsets = UIEdgeInsets(top: 0, left: 0, bottom: keyboardHeight - safeAreaBottom, right: 0)
            
        bodyTextView.contentInset = contentInsets
    }
}
    
@objc func keyboardWillHide(_ sender: Notification) {
    if originalFrame != nil {
        bodyTextView.contentInset = UIEdgeInsets.zero
    }
}
```
- - -
### 2. <TextView Placeholder 구현>

🤯 **문제상황**
기본적으로 `UITextView`에서는 `placeholder` 기능을 제공하지 않아서 직집 구현해야 하는 문제가 있었습니다. 참고로 `placeholder`는 사용자에게 입력하라는 힌트를 주는 메시지 역할로 사용자 입장에서 보다 편리한 UI 경험을 제공하기 위해서 구현하게 되었습니다.

🔥 **해결방법**
`titleTextView`, `bodyTextView`에서 둘다 사용할 수 있도록 `placeHolderText` 문자열을 전역으로 두고 `UITextViewDelegate`의 `textViewDidBeginEditing` `textViewDidEndEditing` 메서드 즉, 텍스트 뷰의 편집의 시작과 종료 시점에서 호출되는 메서드 안에 텍스트와 컬러를 정의함으로서 직접 placeholder기능을 구현하였습니다.

```Swift
let placeHolderText = "Input Text"
...
extension NewDiaryViewController: UITextViewDelegate {
    func textViewDidBeginEditing(_ textView: UITextView) {
        if titleTextView.text == placeHolderText {
            titleTextView.text = nil
            titleTextView.textColor = .black
        }
        
        if bodyTextView.text == placeHolderText {
            bodyTextView.text = nil
            bodyTextView.textColor = .black
        }
    }
    
    func textViewDidEndEditing(_ textView: UITextView) {
        if titleTextView.text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
            titleTextView.text = placeHolderText
            titleTextView.textColor = .lightGray
        }
        
        if bodyTextView.text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
            bodyTextView.text = placeHolderText
            bodyTextView.textColor = .lightGray
        }
    }
}
```
<Img src = "https://hackmd.io/_uploads/rkFAu_2ph.gif" width="300" height="600">
- - -
### 3. <touchesBegan 메서드>
🤯 **문제상황**
`TextViewDelegate`의 `textViewDidEndEditing()`가 호출되기 위해서는 텍스트 뷰의 편집이 종료되어야 하는데 다른 터치이벤트를 이용하여 처리를 해주어야만 편집에서 벗어날 수 있었습니다.

🔥 **해결방법**
`touchesBegan()`는 터치 이벤트가 발생했을 때 호출되는 메서드로 뷰 안에서 편집중인 키보드를 찾아 닫을 수 있도록 해결하였습니다.
```Swift
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    self.view.endEditing(true)
}
```
<Img src = "https://hackmd.io/_uploads/H1T32O2T2.gif" width="300" height="600">

- - -
### 4. <DateFormatterManager 구현>

🤯 **문제상황**
`DateFormatter`를 여러번 생성해서 사용해야 하는 상황이 발생 했습니다. 같은 역할을 하는 인스턴스를 여러번 만들어 사용하는 것이 비효율적이었습니다.


🔥 **해결방법**
`static`을 사용하여 `DateFormatter`를 한번만 생성하여 여러 곳에서 쓸 수 있도록 `CustomDateFormatter` 구조체를 만들었습니다.
```Swift
struct CustomDateFormatter {
    static let customDateFormatter: DateFormatter = {
        let todayDateFormatter = DateFormatter()
        todayDateFormatter.locale = Locale(identifier: "koKR")
        todayDateFormatter.dateFormat = "yyyy년 MM월 dd일"

        return todayDateFormatter
    }()

    static func formatTodayDate() -> String {
        let today = Date()
        let formattedTodayDate = customDateFormatter.string(from: today)

        return formattedTodayDate
    }

    static func formatSampleDate( sampleDate: Int) -> String {
        let timeInterval = TimeInterval(sampleDate)
        let inputDate = Date(timeIntervalSince1970: timeInterval)
        let formattedDate = customDateFormatter.string(from: inputDate)

        return formattedDate
    }
}
```
- - -
### 5. <CoreDataManager 데이터 공유>

🤯 **문제상황**
`CoreDataManager`파일로 CRUD를 구현하였는데 각 ViewController에서 어떤 패턴으로 데이터를 가져와 사용할지 고민하게 되었습니다.

🔥 **해결방법**
단일 인스턴스로 여러 곳에서 해당 인스턴스를 공유할 수 있는 싱글톤 패턴을 사용하였습니다.
```Swift
final class CoreDataManager {
    static var shared: CoreDataManager = CoreDataManager()
    ...
```
- - -
### 6. <tableView 업데이트>

🤯 **문제상황**
텍스트 뷰의 생성하여 저장하거나 수정하여도 변경된 `Entity`데이터가 테이블 뷰에 바로 업데이트 되지 않았고 다시 빌드를 해야만 적용되는 문제가 있었습니다. 

🔥 **해결방법**
`MainVC`가 화면에 뜨기 직전에 호출되는 `viewWillAppear()`가 호출될 때, `getAllEntity()`를 통하여 변경된 Entity의 데이터를 가져오고, 메인 스레드에서 비동기 작업을 통해 테이블 뷰에 다시 데이터를 로드하고 업데이트하는 방식으로 해결했습니다.
```Swift
override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    self.callGetAllEntity()
}

private func callGetAllEntity() {
    coreDataManager.getAllEntity()
    DispatchQueue.main.async {
        self.tableView.reloadData()
    }
}

```

- - -
### 7. <새로운 일기 생성와 기존 일기 수정 처리>

🤯 **문제상황**

이전 화면(리스트 화면)으로 이동하는 경우 저장 되는 기능을 구현하기 위해서 `viewWillDisappear` 메서드 안에 저장하는 기능을 구현했습니다. 하지만 새로 생성하는 일기와 전에 있던 일기를 수정하는 경우를 각각 다르게 처리해야 한다는 문제상황이 발생했습니다.

🔥 **해결방법**

초기에 `tableView`에서 전달받는 `entity`가 있는지 없는지 저장하는 `initEntity` 변수를 만들어주었습니다. 전달 받은 `entity`가 없다면 새로운 일기이므로 `CoreData`에 `createEntity`를 하고 아니라면 기존에 있는 일기이므로 `updateEntity`를 합니다

```Swift=
private var initEntity: Entity?
...

override func viewDidLoad() {
...
        initEntity = self.entity
    }

override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        guard let text = textView.text, !text.isEmpty, text != placeHolderText else {
            return
        }
        let (title, body) = self.splitText(text: text)
        
        if initEntity == nil {
            coreDataManager.createEntity(title: title, body: body)
        } else {
            guard let entity = self.entity else {
                return
            }
            coreDataManager.updateEntity(entity: entity, newTitle: title, newBody: body)
        }
    }
```

- - -
### 8. <다크모드>

🤯 **문제상황**
ViewController에서 직접 다크모드로 값을 주어 선언했을 때, 변경된 배경으로 인해 상단 바와 Title이 보이지 않는 문제가 발생하였습니다.

<Img src = "https://hackmd.io/_uploads/HJX0x9ey6.png" width="300" height="600">

🔥 **해결방법**
SceneDelegate의 scene() 메서드에서 직접 window.overrideUserInterfaceStyle을 dark로 선언해주어 해결하였습니다. 참고로 다크모드는 iOS 13에 도입된 UI 옵션입니다. 또한 SceneDelegate는 멀티 윈도우의 관리를 지원하므로써 Scene 설정을 통해서 다크모드로 지정할 수 있습니다.
```Swift
class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    var window: UIWindow?


    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
        // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
        // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
        guard let _ = (scene as? UIWindowScene) else { return }
        
        if #available(iOS 13.0, *) {
            window?.overrideUserInterfaceStyle = .dark
        }
    }
    ...
```
<Img src = "https://hackmd.io/_uploads/rkA1ZqekT.png" width="300" height="600">

- - -
### 9. <title과 body 분리>

🤯 **문제상황**
텍스트 뷰에 입력한 문자열을 구분하여 title과 body로 어떻게 저장할지 고민하게 되었습니다.

🔥 **해결방법**
텍스트 뷰의 전체 문자열을 들여쓰기를 기준으로 하여 배열로 담아 분류하여 첫 번째 요소를 title로 주고 이후 나머지 값을 모두 body로 처리하여 구분하였습니다.
```Swift
private func splitText(text: String) -> (title: String, body: String) {
    let lines = text.components(separatedBy: "\n")
    var title = ""
    var body = ""
    
    if let firstLine = lines.first {
        title = firstLine
    }
    
    if lines.count > 1 {
        body = lines[2...]
            .joined(separator: "\n")
    }
    
    return (title, body)
}
```
- - -
<a id="6."></a>

## 6. 팀 회고

### 우리팀이 잘한점👍
- 이해를 하는 데에 중심을 두고 프로젝트를 진행해서 공부를 많이 하게 되었습니다.
    
### 서로에게 피드백😀
    
- <To. yyss99>
    - 적극적으로 질문을 해주셔서 저 또한 다시 이해해보고 공부하게 되는 시간이 될 수 있었습니다.
    - 제 의견과 설명을 해드리면 바로 포인트를 캐치해서 빠르게 이해해주셔서 좋았습니다.

- <To. Jusbug🕷️>
    - 의견을 제시했을 때 잘 반영해주셔서 좋았습니다.👍
    - 프로젝트 진행보다 이해와 학습 우선시 하는 분위기를 만들어 주셔서 좋았습니다.📖
    
</br>

- - -
<a id="7."></a>

## 7. 참고 문서

- [🍎 Apple - Adaptivity and Layout](https://developer.apple.com/design/human-interface-guidelines/ios/visual-design/adaptivity-and-layout/)
- [🍎 Apple - UIKit: Apps for Every Size and Shape
](https://www.wwdcnotes.com/notes/wwdc18/235/)
- [🎦 Video - Making apps adaptive 1](https://www.youtube.com/watch?v=hLkqt2g-450)
- [🎦 Video - Making apps adaptive 2](https://www.youtube.com/watch?v=s3utpBiRbB0)
- [🍎 Apple - dateformatter](https://developer.apple.com/documentation/foundation/dateformatter)
- [🍎 Apple - UITextView](https://developer.apple.com/documentation/uikit/uitextview)
- [🍎 Apple - coredata](https://developer.apple.com/documentation/coredata)
- [🍎 Apple - UItextviewdelegate](https://developer.apple.com/documentation/uikit/uitextviewdelegate)
- [🍎 Apple - UIswipeactionsconfiguration](https://developer.apple.com/documentation/uikit/uiswipeactionsconfiguration)
- [🍎 Apple - UIsearchcontroller](https://developer.apple.com/documentation/uikit/uisearchcontroller)
- [🍎 Apple - dark-mode](https://developer.apple.com/design/human-interface-guidelines/ios/visual-design/dark-mode)
- [🎦 Video - Typography and Fonts (WWDC 2016)](https://www.youtube.com/watch?v=7AeEkoKb52Y)