# ์ผ๊ธฐ์ฅ[STEP2]
> ๐ ํ๋ฉด์ ์ ๋ชฉ, ์์ฑ์ผ์, ํ์ค ๋ฏธ๋ฆฌ๋ณด๊ธฐ๊ฐ ๋ณด์ฌ์ง๋ ์ผ๊ธฐ์ฅ ์ฑ์
๋๋ค.
> **ํต์ฌ ๊ฐ๋
**
UITextView, UITableView, DateFormatter, NotificationCenter, CoreData, UISwipeActionsConfiguration, UIActivityViewController
## ๐ ๋ชฉ์ฐจ</br>
- [ํ์์๊ฐ](#-ํ์-์๊ฐ)
- [ํ์ผํธ๋ฆฌ](#-ํ์ผํธ๋ฆฌ)
- [์๊ฐํ๋ ํ๋ก์ ํธ ๊ตฌ์กฐ](#์๊ฐํ๋-ํ๋ก์ ํธ-๊ตฌ์กฐ)
- [ํ์๋ผ์ธ](#-ํ์๋ผ์ธ)
- [์คํํ๋ฉด](#-์คํํ๋ฉด)
- [ํธ๋ฌ๋ธ ์ํ
](#-ํธ๋ฌ๋ธ-์ํ
)
- [์ฐธ๊ณ ์๋ฃ](#-์ฐธ๊ณ ์๋ฃ)
## ๐งโ๐ป ํ์ ์๊ฐ</br>
| <img src="https://github.com/devKobe24/images/blob/main/%E1%84%86%E1%85%AE%E1%86%AB.jpeg?raw=true" width="200" height="200"/> | <img src="https://github.com/devKobe24/BranchTest/blob/main/IMG_5424.JPG?raw=true" width="200" height="200"/> |
| :-: | :-: |
| [**Moon(๋ฌธ)**](https://github.com/hojun-jo) | [**Kobe(์ฝ๋น)**](https://github.com/devKobe24) |
## ๐๏ธ ํ์ผํธ๋ฆฌ</br>
```
.
โโโ Diary
โย ย โโโ Model
โย ย โย ย โโโ DiaryDateFormatter.swift
โย ย โย ย โโโ Protocol
โย ย โย ย โโโ IdentifierGenerator.swift
โย ย โโโ View
โย ย โ โโโ Base.lproj
โย ย โ โย ย โโโ LaunchScreen.storyboard
โย ย โ โโโ DiaryTableViewCell.swift
โย ย โโโ Controller
โย ย โย ย โโโ DetailDiaryViewController.swift
โย ย โย ย โโโ DiaryMainViewController.swift
โย ย โโโ Application
โย ย โย ย โโโ AppDelegate.swift
โย ย โย ย โโโ DiaryPersistentContainer.swift
โย ย โย ย โโโ SceneDelegate.swift
โย ย โโโ Extension
โย ย โย ย โโโ Array+.swift
โย ย โย ย โโโ UITableViewCell+.swift
โย ย โโโResource
โย ย ย ย โโโ Assets.xcassets
โย ย ย ย โโโ AccentColor.colorset
โย ย ย ย โย ย โโโ Contents.json
โย ย ย ย โโโ AppIcon.appiconset
โย ย ย ย โย ย โโโ Contents.json
โย ย ย ย โโโ Contents.json
โโโ README.md
```
## ๐บ๏ธ ์๊ฐํ๋ ํ๋ก์ ํธ ๊ตฌ์กฐ</br>
<img src = "https://github.com/devKobe24/images/blob/main/Diary%5BSTEP2%5DUML.png?raw=true">
## โฐ ํ์๋ผ์ธ</br>
ํ๋ก์ ํธ ์งํ ๊ธฐ๊ฐ | 23.08.29.(ํ) ~ 23.09.15.(๊ธ)
| ๋ ์ง | ์งํ ์ฌํญ |
| -------- | -------- |
| 23.08.29.(ํ) | SwiftLint ์ ์ฉ.<br/>ํ
์ด๋ธ ๋ทฐ ์์ฑ, Autolayout ์ ์ฉ.<br/>DiaryTableViewCell ์์ฑ ๋ฐ ๊ตฌํ.<br/>UITableViewCell Extension ๊ตฌํ.<br/>Main.storyboard ์ญ์ .<br/>DiaryTableViewCell UI ์์ <br/>๋ค๋น๊ฒ์ด์
์์ดํ
์ถ๊ฐ<br/>DateFormatter extension ๊ตฌํ|
| 23.08.30.(์) | Diary DTO ์์ฑ<br/>DecodeError ์์ฑ ๋ฐ ๊ตฌํ<br/>์ํ ์์
์ถ๊ฐ<br/>์ํ ๋ฐ์ดํฐ ๋์ฝ๋ฉ<br/>NSAttributedString ๋ฐํ ํจ์ ์์ <br/>fetchDate ํจ์ ์์ฑ ๋ฐ ๊ตฌํ.<br/>formatCreatedAt ํจ์ ์์ฑ ๋ฐ ๊ตฌํ.<br/>DiaryDateFormatter ์์ฑ ๋ฐ ๊ตฌํ
| 23.08.31.(๋ชฉ) | DateFormatter, UITextView ๊ฐ๋
ํ์ต<br/>
| 23.09.01(๊ธ) | ์ผ๊ธฐ์ฅ [STEP1-1] README ์์ฑ.<br/>
| 23.09.02(ํ ) | DetailDiaryViewController ์์ฑ<br>TextView ๊ตฌํ.<br/>TextViewDelegate ์ค์ <br>keyboardDismissMode ์ค์
| 23.09.06(์) | TextView keyboardDismissMode ์ต์
์ถ๊ฐ<br>์ถ์ํ ๋ ๋ฒจ ํต์ผ<br>์ ๊ทผ์ ํ์ ์์ <br>Array extension ์์ฑ ๋ฐ subscript ๊ตฌํ<br>index ์ ๊ทผ์ safe subscript๋ก ์์ <br>iOS 15 ๋ฒ์ ์ ๊ธฐ์ค์ผ๋ก diaryTextView layout constraints ๋ถ๊ธฐ ์ฒ๋ฆฌ.<br/>
| 23.09.08(๊ธ) | ์ผ๊ธฐ์ฅ [STEP1] README ์์ฑ.<br/>
| 23.09.09(ํ ) | ํ๋ก์ ํธ ๊ด๋ จ ํ์ ๋ฐ ๊ฐ๋
ํ์ต.<br/>
| 23.09.10(์ผ) | tableView(_ :, didSelectRowAt:) ์์ฑ ๋ฐ ํ๋ฉด ๋ณ๊ฒฝ ๋ก์ง ๊ตฌํ.<br>tableView(_ :, trailingSwipeActionsConfigurationForRowAt: ) ์์ฑ ๋ฐ ์ค์์ดํ ์ญ์ ๊ธฐ๋ฅ ๊ตฌํ.<br>ableView(_ :, trailingSwipeActionsConfigurationForRowAt: )ํจ์ ๋ด๋ถ ์ค์์ดํ share ๊ธฐ๋ฅ ๊ตฌํ.<br><br/>
| 23.09.11(์) | ํ๋ก์ ํธ ๊ด๋ จ ํ์ ๋ฐ ๊ฐ๋
ํ์ต.<br/>
| 23.09.12(ํ) | core data ์๋ฌ ์์ ๋ฐ leftBarButtonItem์ backBarButtonItem์ผ๋ก ๋ณ๊ฒฝ<br/>
| 23.09.13(์) | ํ๋ก์ ํธ ๊ด๋ จ ํ์ ๋ฐ ๊ฐ๋
ํ์ต.<br/>
| 23.09.14(๋ชฉ) | ํ๋ก์ ํธ ๊ด๋ จ ํ์ ๋ฐ ๊ฐ๋
ํ์ต.<br/>
| 23.09.15(๊ธ) | ์ด์ ํ๋ฉด์ผ๋ก ์ ํ ์ ๋ฐ์ดํฐ๊ฐ ๋ฐ๋ก ์ ์ฅ๋์ง ์๋ ์ค๋ฅ ์์ <br>์ผ๊ธฐ์ฅ [STEP2] README ์์ฑ.<br/>
## ๐บ ์คํํ๋ฉด</br>
- STEP1 ์ผ๊ธฐ์ฅ ์๋ฎฌ๋ ์ดํฐ ์คํํ๋ฉด ๐ฌ </br>
<img src = "https://github.com/devKobe24/images/blob/main/%E1%84%8B%E1%85%B5%E1%86%AF%E1%84%80%E1%85%B5%E1%84%8C%E1%85%A1%E1%86%BCstep2-g.gif?raw=true">
## ๐จ ํธ๋ฌ๋ธ ์ํ
### 1๏ธโฃ **StackView ๋ด๋ถ์์ Label์ Height๊ฐ ์กํ์ง ์๋ ํ์.**</br>
### ๐ **๋ฌธ์ ์ ** ๐</br>
**๐จ StackView ๋ด๋ถ์์ Label์ Height๊ฐ ์กํ์ง ์๋ ํ์์ด ์์์ต๋๋ค.</br>height is ambiguous for UILabel ๊ฒฝ๊ณ ๊ฐ ์๊ฒจ ํธ๋ฌ๋ธ ์ํ
์ ์งํํ๊ฒ ๋์์ต๋๋ค.**</br>
### ๐ **ํด๊ฒฐ๋ฐฉ๋ฒ** ๐</br>
**๐โโ๏ธ diaryTitle๊ณผ dateAndPreview์ content hugging priority ๊ฐ ๊ฐ์ ์๊ธฐ๋ ํ์์ด์์ต๋๋ค.</br>๋ฐ๋ผ์ diaryTitle์ .defaultHigh + 1 ๊ฐ์ ์ฃผ์๊ณ , dateAndPreview์๋ .defaultHigh๊ฐ์ ์ฃผ์ด ๊ฐ๊ฐ ๋ค๋ฅธ content hugging priority๊ฐ์ ์ฃผ์ด ํด๊ฒฐํ์์ต๋๋ค.**
```swift
import UIKit
class DiaryTableViewCell: UITableViewCell {
private let diaryTitle: UILabel = {
let label = UILabel()
label.font = UIFont.preferredFont(forTextStyle: .title2)
label.setContentHuggingPriority(.defaultHigh + 1, for: .vertical)
return label
}()
private let dateAndPreview: UILabel = {
let label = UILabel()
label.font = UIFont.preferredFont(forTextStyle: .body)
label.setContentHuggingPriority(.defaultHigh, for: .vertical)
return label
}()
}
```
### 2๏ธโฃ **NSAttributedString์ด ์ ์ฉ๋์ง ์๋ ํ์.** </br>
### ๐ **๋ฌธ์ ์ ** ๐</br>
**๐จ [__NSCFConstantString renderingMode]: unrecognized selector sent to instance 0x10aa5a2e8 ์๋ฌ๊ฐ ๋ฐ์ํ์ต๋๋ค.
์์ธ์ NSAttributedString.Key.font์ ํ์ํ ํ์
์ด UIFont์์ผ๋ UIFont.TextStyle์ ์ฌ์ฉ ์ค์ด์์ต๋๋ค.**
```swift
private func convertAttributedString(text: String, font: UIFont.TextStyle) -> NSAttributedString {
let attributes = [NSAttributedString.Key.font: font as Any] as [NSAttributedString.Key : Any]
let attributedString = NSAttributedString(string: text, attributes: attributes)
return attributedString
}
```
### ๐ **ํด๊ฒฐ๋ฐฉ๋ฒ** ๐</br>
๐โโ๏ธ**NSAttributedString.Key.font์ UIFont ํ์
์ผ๋ก ์ ๋ฌํ์ฌ ํด๊ฒฐํ์ต๋๋ค.**</br>
```swift
private func convertAttributedString(text: String, font: UIFont) -> NSAttributedString {
let attributes = [NSAttributedString.Key.font: font as Any] as [NSAttributedString.Key : Any]
let attributedString = NSAttributedString(string: text, attributes: attributes)
return attributedString
}
```
### 3๏ธโฃ NSAttributedString์ ๊ฐ์ด๋ฐ ์ ๋ ฌ
### ๐ **๋ฌธ์ ์ ** ๐</br>
**๐จ ๋ฌธ์์ด ์์ฒด์ ๋ชจ์์ด ์๋ ์ด๋ฏธ์ง์ ๊ฐ๊ธฐ ๋๋ฌธ์ UILabel์ baselineAdjustment๋ก ๊ฐ์ด๋ฐ ์ ๋ ฌ์ ๋ง์ถ ์ ์์์ต๋๋ค.**
<img src = "https://github.com/devKobe24/images/blob/main/NSAttributedString%E1%84%86%E1%85%AE%E1%86%AB%E1%84%8C%E1%85%A6%E1%84%8C%E1%85%A5%E1%86%B7.png?raw=true"></br>
### ๐ **ํด๊ฒฐ๋ฐฉ๋ฒ** ๐</br>
**๐โโ๏ธNSAttributedString.Key.baselineOffset์ ์ด์ฉํด ์์ ๋ฌธ์ ๋ถ๋ถ์ baseline์ ์ฌ๋ฆฌ๋ ๊ฒ์ผ๋ก ํด๊ฒฐํ์ต๋๋ค.**</br>
<img src = "https://github.com/devKobe24/images/blob/main/NSAttributedString%E1%84%92%E1%85%A2%E1%84%80%E1%85%A7%E1%86%AF%E1%84%8C%E1%85%A5%E1%86%B7.png?raw=true"></br>
```swift
private func attributedDateAndPreview(data: Diary, font: UIFont) -> NSMutableAttributedString {
let text = "\(formatCreatedAt(data.createdAt)) \(data.body)"
let attributedString = NSMutableAttributedString(string: text)
let attributes: [NSAttributedString.Key: Any] = [
.font: font,
.baselineOffset: 2
]
attributedString.addAttributes(attributes, range: (text as NSString).range(of: data.body))
return attributedString
}
```
### 4๏ธโฃ **ํ
์คํธ๋ฅผ ๊ธธ๊ฒ ์์ฑ์ ํค๋ณด๋์ ๊ฐ๋ฆฌ๋ ๋ฌธ์ ๋ฐ์.**
### ๐ **๋ฌธ์ ์ ** ๐</br>
**๐จ ํ
์คํธ๊ฐ ๊ธธ์ด์ง์ ๋ฐ๋ผ ํค๋ณด๋ ์๋จ์ ๊ฐ๋ ค์ ธ ์๋์ ์ผ๋ก ํค๋ณด๋๋ฅผ ๋ด๋ฆฌ๊ฑฐ๋ ์ ์ ๊ฐ ์คํฌ๋กค๋ฅผ ํ์ฌ ํ
์คํธ๋ฅผ ๋ด์ผํ๋ ๋ฌธ์ ๊ฐ ์๊ฒผ์ต๋๋ค.**
### ๐ **ํด๊ฒฐ๋ฐฉ๋ฒ** ๐</br>
**๐โโ๏ธ ๋
ธํฐํผ์ผ์ด์
์ผํฐ๋ฅผ ํ์ฉํ์์ต๋๋ค. ๊ทธ ์ค `UIResponder.keyboardWillHideNotification`์ `UIResponder.keyboardWillShowNotification`๋ฅผ ์ด์ฉํ์ฌ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ์์ต๋๋ค. ํด๊ฒฐํ ์ฝ๋๋ ์๋์ ๊ฐ์ต๋๋ค.**</br>
```swift
extension DetailDiaryViewController {
private func setupKeyboardEvent() {
if #unavailable(iOS 15.0) {
NotificationCenter.default.addObserver(
self,
selector: #selector(keyboardWillShow),
name: UIResponder.keyboardWillShowNotification,
object: nil
)
NotificationCenter.default.addObserver(
self,
selector: #selector(keyboardWillHide),
name: UIResponder.keyboardWillHideNotification,
object: nil
)
}
}
@objc private func keyboardWillShow(_ sender: Notification) {
guard let userInfo = sender.userInfo,
let keyboardFrame = userInfo[
UIResponder.keyboardFrameEndUserInfoKey
] as? CGRect
else {
return
}
diaryTextView.contentInset = UIEdgeInsets(
top: .zero,
left: .zero,
bottom: keyboardFrame.height,
right: .zero
)
}
@objc private func keyboardWillHide() {
diaryTextView.contentInset = UIEdgeInsets()
}
}
```
### 5๏ธโฃ **์ผ๊ธฐ์ ๋งจ ์ฒซ ์ค์ ์ผ๊ธฐ์ ์ ๋ชฉ์ด ๋๊ณ , ๊ทธ ๋ค์ ์ค๋ถํฐ ๋ณธ๋ฌธ์ด ๋๋ ๋ก์ง.**
### ๐ **๋ฌธ์ ์ ** ๐</br>
**๐จ ์ผ๊ธฐ์ ๋งจ ์ฒซ ์ค์ ์ผ๊ธฐ์ ์ ๋ชฉ์ด ๋๊ฒ ํ๊ณ , ๊ทธ ๋ค์ ์ค๋ถํฐ ๋ณธ๋ฌธ์ด ๋๋๋ก ํ๋ ๋ก์ง์ ๋ง๋๋ ๋ถ๋ถ์์ ํ
์คํธ ๋ทฐ์ ์ฒซ ๋ฒ์งธ ์ค๊ณผ ๊ทธ ๋ค์ ์ค์ ์ด๋ค ๊ธฐ์ค์ผ๋ก ๋๋์ด์ผ ํ๋์ง ์ ์ ์์ด ๋ฌธ์ ๊ฐ ๋์์ต๋๋ค.**
### ๐ **ํด๊ฒฐ๋ฐฉ๋ฒ** ๐</br>
**๐โโ๏ธํ
์คํธ ๋ทฐ์ ๋งจ ์ฒซ ์ค์ ์ผ๊ธฐ์ ์ ๋ชฉ์ด ๋๊ณ , ๊ทธ ๋ค์ ์ค๋ถํฐ ๋ณธ๋ฌธ์ด ๋๋๋ก ํ๋ ๋ก์ง์ ๋ํ์ฌ ๋ง์ ๊ณ ๋ฏผ์ ํ๊ฒ ๋์์ต๋๋ค.</br>split(separator:), ๋ฐฐ์ด, dropFirst(_:)๋ฅผ ํ์ฉํ์ฌ ๋ก์ง์ ๋ง๋ค์์ต๋๋ค. ๊ทธ๋์ ํ
์คํธ ๋ทฐ์ ํ
์คํธ์ "\n" ์ค๋ฐ๊ฟ์ ๊ธฐ์ค์ผ๋ก String.SubSequence ๋ฐฐ์ด์ ๋ง๋ค๊ณ ๊ทธ ๋ฐฐ์ด์ index 0๋ฒ์งธ๋ฅผ ์ผ๊ธฐ์ ์ ๋ชฉ์ผ๋ก ๋ง๋ค๊ณ , dropFirst(์ ๋ชฉ.count)๋ฅผ ํ์ฉํ์ฌ ์ ๋ชฉ count ์ด์์ ๋ณธ๋ฌธ์ผ๋ก ๋ง๋๋ ๊ฒ์ผ๋ก ํด๊ฒฐํ์์ต๋๋ค.**</br>
```swift
let splitedText = diaryTextView.text.split(separator: "\n")
let title = String(splitedText[safe: 0] ?? .init())
let body = String(diaryTextView.text.dropFirst(title.count))
```
### 6๏ธโฃ **๋ฐฑ๊ทธ๋ผ์ด๋ ์ง์
์ ๋ฐ์ดํฐ ์ ์ฅ.**
### ๐ **๋ฌธ์ ์ ** ๐</br>
**๐จ ์ปจํ
์คํธ๋ง ์ ์ฅํ ๊ฒฝ์ฐ ์ผ๊ธฐ์์ ์์ ์ค์ธ ๋ด์ฉ์ด ๋ฐ์๋์ง ์์์ต๋๋ค.
์๋์ฐ๋ฅผ ํตํด ๋ทฐ ์ปจํธ๋กค๋ฌ๋ฅผ ๊ฐ์ ธ์ ์ ์ฅํ๋ ์์
์ด ํ์ํ ๋ฏํ์ฌ ๋ค์๊ณผ ๊ฐ์ด ์๋ ํ์ผ๋ ์คํจํ์ต๋๋ค.**</br>
```swift
func sceneDidEnterBackground(_ scene: UIScene) {
guard let diaryViewController = window?.rootViewController?.navigationController?.topViewController as? DetailDiaryViewController else {
return
}
diaryViewController.saveDiaryData()
// persistentContainer?.saveContext()
}
```
### ๐ **ํด๊ฒฐ๋ฐฉ๋ฒ** ๐</br>
**๐โโ๏ธ ๋ฃจํธ ๋ทฐ ์ปจํธ๋กค๋ฌ๊ฐ ๋ค๋น๊ฒ์ด์
์ปจํธ๋กค๋ฌ์๊ธฐ ๋๋ฌธ์ ์๋์ ๊ฐ์ด ์์ ํ์์ต๋๋ค.**</br>
```swift
func sceneDidEnterBackground(_ scene: UIScene) {
guard let navigationController = window?.rootViewController as? UINavigationController,
let diaryViewController = navigationController.topViewController as? DetailDiaryViewController else {
return
}
diaryViewController.saveDiaryData()
}
```
### 7๏ธโฃ **์ด์ ํ๋ฉด์ผ๋ก ์ ํ ์ ๋ฐ์ดํฐ ์ ์ฅ.**
### ๐ **๋ฌธ์ ์ ** ๐</br>
**๐จ backBarButtonItem์ ์ด์ฉํด ์ด์ ํ๋ฉด์ผ๋ก ์ ํํ ๊ฒฝ์ฐ UIBarButtonItem์ action์ผ๋ก ์ง์
ํ์ง ์๋ ํ์์ด ๋ฐ์ํ์ต๋๋ค. ์ด๋ก ์ธํด ๋ฐ์ดํฐ๋ฅผ ๋ฐ๋ก ์ ์ฅํ์ง ๋ชป ํ๋ ์ํฉ์ด ์ผ์ด๋ฌ์ต๋๋ค.**</br>
```swift
private func configureNavigationItem(date: Date = Date()) {
...
navigationItem.backBarButtonItem = UIBarButtonItem(title: "์ผ๊ธฐ์ฅ", style: .plain, target: self, action: #selector(didTapBackToMainButton))
...
}
@objc private func didTapBackToMainButton() {
saveDiaryData()
navigationController?.popViewController(animated: true)
}
```
### ๐ **ํด๊ฒฐ๋ฐฉ๋ฒ** ๐</br>
**๐โโ๏ธ viewWillDisappear์์ ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํ๋๋ก ํ์ฌ ํ๋ฉด ์ ํ ์ ํญ์ ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํ ์ ์๋๋ก ํ์์ต๋๋ค.**</br>
```swift
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
saveDiaryData()
}
```
## ๐ ์ฐธ๊ณ ์๋ฃ
- [๐ Adaptivity and Layout](https://developer.apple.com/design/human-interface-guidelines/layout)
- [๐ UIKit: Apps for Every Size and Shape](https://www.wwdcnotes.com/notes/wwdc18/235/)
- [๐ฅ Making apps adaptive part 1](https://www.youtube.com/watch?v=hLkqt2g-450)
- [๐ฅ Making apps adaptive part 2](https://www.youtube.com/watch?v=s3utpBiRbB0)
- [๐ DateFormatter](https://developer.apple.com/documentation/foundation/dateformatter)
- [๐ UITextView](https://developer.apple.com/documentation/uikit/uitextview)
- [๐ NotificationCenter](https://developer.apple.com/documentation/foundation/notificationcenter)
- [๐ UIResponder](https://developer.apple.com/documentation/uikit/uiresponder)
- [๐ contentInset](https://developer.apple.com/documentation/uikit/uiscrollview/1619406-contentinset)
- [๐บ SwiftLint](https://github.com/realm/SwiftLint)
- [๐ Core Data](https://developer.apple.com/documentation/coredata)
- [๐ฅ Making Apps with Core Data](https://developer.apple.com/videos/play/wwdc2019/230/)
- [๐ UITextViewDelegate](https://developer.apple.com/documentation/uikit/uitextviewdelegate)
- [๐ UISwipeActionsConfiguration](https://developer.apple.com/documentation/uikit/uiswipeactionsconfiguration)