# ๋‹ค์ด์–ด๋ฆฌ - REAEME # ๐Ÿ“’ Diary CoreData๋ฅผ ์ด์šฉํ•ด ๊ธฐ๋ก์„ ๋‚จ๊ธฐ๋Š” ์ผ๊ธฐ์žฅ ์–ดํ”Œ์ž…๋‹ˆ๋‹ค. - ์ƒˆ๋กœ์šด ์ผ๊ธฐ๋ฅผ ์ €์žฅํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. - ์ž๋™ ์ €์žฅ๊ธฐ๋Šฅ์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค. - ์ €์žฅ๋œ ์ผ๊ธฐ๋ฅผ ํ™•์ธ / ์ˆ˜์ •/ ๊ณต์œ  / ์‚ญ์ œ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. - ๊ฐ€์žฅ ์ตœ๊ทผ์ˆœ์œผ๋กœ ์ €์žฅ๋œ ์ผ๊ธฐ๋ฆฌ์ŠคํŠธ๋ฅผ ํ™•์ธ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. - ์ €์žฅ๋œ ์ผ๊ธฐ๋ฅผ ํ„ฐ์น˜ํ•ด ํ™•์ธํ•˜๋Š” ํ™”๋ฉด์—์„œ ์ˆ˜์ •์ด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. - ์ˆ˜์ •๋‚ด์šฉ์„ ์ž๋™ ์ €์žฅํ•˜๋Š” ๊ธฐ๋Šฅ์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค. - ์ผ๊ธฐ๋ฅผ ๋ณต์‚ฌํ•˜๊ฑฐ๋‚˜ ์™ธ๋ถ€ SNS๋กœ ๊ณต์œ ํ•˜๋Š” ๋‚ด๋ณด๋‚ด๊ธฐ ์„œ๋น„์Šค๋ฅผ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค. - ๋ฆฌ์ŠคํŠธ ๋ชฉ๋ก์—์„œ Swipeํ•˜๊ฑฐ๋‚˜ ์ƒ์„ธํ™”๋ฉด์˜ ๋ฒ„ํŠผ์„ ์ด์šฉํ•ด ๊ณต์œ /์‚ญ์ œ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. - ๊ธฐ๊ธฐ์˜ ์œ„์น˜์— ๋งž๋Š” ๋‚ ์”จ๋ฅผ ํŒŒ์•…ํ•ด ์ž๋™ ๊ธฐ๋กํ•ฉ๋‹ˆ๋‹ค. - ์‚ฌ์šฉ์ž์˜ ์œ„์น˜ ์ •๋ณด๋ฅผ ์š”์ฒญํ•˜๊ณ , ๊ถŒํ•œ์ด ํ—ˆ๋ฝ๋˜๋ฉด ๊ธฐ๊ธฐ์˜ ์œ„์น˜๋ฅผ ์ž๋™ํŒŒ์•… ํ•ฉ๋‹ˆ๋‹ค. - ์ €์žฅ๋œ ๋‹ค์ด์–ด๋ฆฌ์˜ ํ•ด๋‹น ๋‚ ์งœ์˜ ๋‚ ์”จ ์•„์ด์ฝ˜์„ ๋ฆฌ์ŠคํŠธ์— ํ•จ๊ป˜ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค. - ๊ฐ€๋กœ๋ชจ๋“œ๋ฅผ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค. - ํฐ ๊ธฐ๊ธฐ์˜ ๊ฐ€๋กœ๋ชจ๋“œ์—์„œ๋„ ์‚ฌ์šฉ์ž๊ฐ€ ์ฝ๊ธฐ ํŽธ์•ˆํ•œ ์ปจํ…์ธ  ๋ ˆ์ด์•„์›ƒ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ## ๐Ÿ“– ๋ชฉ์ฐจ 1. [ํŒ€ ์†Œ๊ฐœ](#-ํŒ€-์†Œ๊ฐœ) 2. [๊ธฐ๋Šฅ ์†Œ๊ฐœ](#-๊ธฐ๋Šฅ-์†Œ๊ฐœ) 3. [Class Diagram](#-class-diagram) 4. [ํด๋” ๊ตฌ์กฐ](#-ํด๋”-๊ตฌ์กฐ) 5. [ํ”„๋กœ์ ํŠธ์—์„œ ๊ฒฝํ—˜ํ•˜๊ณ  ๋ฐฐ์šด ๊ฒƒ](#-ํ”„๋กœ์ ํŠธ์—์„œ-๊ฒฝํ—˜ํ•˜๊ณ -๋ฐฐ์šด-๊ฒƒ) 6. [ํƒ€์ž„๋ผ์ธ](#-ํƒ€์ž„๋ผ์ธ) 7. [๊ณ ๋ฏผํ•œ ๋ถ€๋ถ„](#-๊ณ ๋ฏผํ•œ-๋ถ€๋ถ„) 8. [ํŠธ๋Ÿฌ๋ธ” ์ŠˆํŒ…](#-ํŠธ๋Ÿฌ๋ธ”-์ŠˆํŒ…) 9. [์ฐธ๊ณ  ๋งํฌ](#-์ฐธ๊ณ -๋งํฌ) --- ## ๐ŸŒฑ ํŒ€ ์†Œ๊ฐœ |[์จ๋‹ˆ์ฟ ํ‚ค](https://github.com/sunny-maeng)|[LJ](https://github.com/lj-7-77)| |:---:|:---:| |<img width="180px" img style="border: 2px solid lightgray; border-radius: 90px;-moz-border-radius: 90px;-khtml-border-radius: 90px;-webkit-border-radius: 90px;" src="https://avatars.githubusercontent.com/u/107384230?v=4">| <img width="180px" img style="border: 2px solid lightgray; border-radius: 90px;-moz-border-radius: 90px;-khtml-border-radius: 90px;-webkit-border-radius: 90px;" src="https://i.imgur.com/ggU7PLR.jpg">| --- ## ๐Ÿ›  ๊ธฐ๋Šฅ ์†Œ๊ฐœ <details> <summary>[Step1 - ๋‹ค์ด์–ด๋ฆฌ ๋ฆฌ์ŠคํŠธ ํ™”๋ฉด, ์ƒ์„ธํ™”๋ฉด] </summary> | **๋‹ค์ด์–ด๋ฆฌ ๋ฆฌ์ŠคํŠธ ํ™”๋ฉด** | | :-------------------------------------------: | | <img src = "https://i.imgur.com/m8kSEaS.gif"> | **๋‹ค์ด์–ด๋ฆฌ ์ƒ์„ธ ํ™”๋ฉด** | | <img src = "https://i.imgur.com/G7qLxH3.gif"> | </details> </br> [Step2 - ๋‹ค์ด์–ด๋ฆฌ CRUD] | **์ƒ์„ฑ** | **์ฝ๊ธฐ** | **์ˆ˜์ •** | | :---: | :---: | :---: | | <img src = "https://i.imgur.com/M9rvhco.gif"> | <img src = "https://i.imgur.com/sqZlb0s.gif"> | <img src = "https://i.imgur.com/hic34ow.gif"> | | **์‚ญ์ œ** | **๊ณต์œ ,์ทจ์†Œ** | **์Šค์™€์ดํ”„** | | :---: | :---: | :---: | | <img src = "https://i.imgur.com/zb96B1F.gif"> | <img src = "https://i.imgur.com/I7r5Ysy.gif"> | <img src = "https://i.imgur.com/biRoxz9.gif"> | **Step3 - ๋‚ ์”จ API ํ™œ์šฉ** | **Lightweight Migration** | **์œ„์น˜์ •๋ณด ๊ถŒํ•œ ์š”์ฒญ** | **๋‚ ์”จ ์•„์ด์ฝ˜ ํ‘œ์‹œ** | | :---: | :---: | :---: | | <img width = 280, src = "https://i.imgur.com/vpsaRkh.png"> | <img width = 300, src = "https://i.imgur.com/d4pWZX7.png"> | <img width = 310, src = "https://i.imgur.com/rT75YW4.png"> | --- ## ๐Ÿ‘€ Class Diagram | **Model** | | :-------------------------------------------: | |![](https://i.imgur.com/7exbgco.png)| | **ViewController** | | :-------------------------------------------: | |![](https://i.imgur.com/FhbiC1r.png)| --- ## ๐Ÿ—‚ ํด๋” ๊ตฌ์กฐ ``` Diary โ”œโ”€โ”€ Model โ”‚ย ย  โ”œโ”€โ”€ DiaryInfo โ”‚ย ย  โ”œโ”€โ”€ WeatherInfo โ”‚ย ย  โ”œโ”€โ”€ Weather โ”‚ย ย  โ”‚ย ย  โ”œโ”€โ”€ LocationManager โ”‚ย ย  โ”‚ย ย  โ”œโ”€โ”€ WeatherManager โ”‚ย ย  โ”‚ย ย  โ””โ”€โ”€ WeatherURL โ”‚ย ย  โ”œโ”€โ”€ CoreData โ”‚ย ย  โ”‚ย ย  โ”œโ”€โ”€ CoreDataManager โ”‚ย ย  โ”‚ย ย  โ””โ”€โ”€ MappingModelV1ToV2.xcmappingmodel โ”‚ย ย  โ”œโ”€โ”€ URLSession โ”‚ย ย  โ”‚ย ย  โ””โ”€โ”€ URLSessionProvider โ”‚ย ย  โ”œโ”€โ”€ Support โ”‚ย ย  โ”‚ย ย  โ”œโ”€โ”€ CacheManager โ”‚ย ย  โ”‚ย ย  โ””โ”€โ”€ DecodeManager โ”‚ย ย  โ”œโ”€โ”€ Extension โ”‚ย ย  โ”‚ย ย  โ””โ”€โ”€ Date+Extension โ”œโ”€โ”€ View โ”‚ย ย  โ”œโ”€โ”€ DiaryListView โ”‚ย ย  โ”œโ”€โ”€ DiaryCell โ”‚ย ย  โ”œโ”€โ”€ DiaryDetailView โ”‚ย ย  โ””โ”€โ”€ CustomUI โ”œโ”€โ”€ ViewController โ”‚ โ”œโ”€โ”€ DiaryListViewController โ”‚ โ”œโ”€โ”€ DiaryDetailViewController โ”‚ โ”œโ”€โ”€ RegisterDiaryViewController โ”‚ โ””โ”€โ”€ CustomActivityViewController โ”œโ”€โ”€ AppDelegate โ”œโ”€โ”€ SceneDelegate โ””โ”€โ”€ Info.plist โ”œโ”€โ”€ Diary.xcdatamodeld โ”‚ย ย  โ”œโ”€โ”€ Diary 2.xcdatamodel โ”‚ย ย  โ””โ”€โ”€ Diary.xcdatamodel Pods โ””โ”€โ”€ SwiftLint ``` --- ## ํ”„๋กœ์ ํŠธ์—์„œ ๊ฒฝํ—˜ํ•˜๊ณ  ๋ฐฐ์šด ๊ฒƒ - CoreData ํ™œ์šฉํ•˜๊ธฐ - [X] CoreData ๋ชจ๋ธ์„ ์‹ฑ๊ธ€ํ†ค์œผ๋กœ ์ƒ์„ฑํ–ˆ์Šต๋‹ˆ๋‹ค. - [X] CRUD ๋ฉ”์„œ๋“œ๋ฅผ ๊ตฌํ˜„ํ–ˆ์Šต๋‹ˆ๋‹ค. - CoreData Migration ์ ์šฉํ•˜๊ธฐ - [X] ๋‚ ์”จ์ •๋ณด๋ฅผ ์ถ”๊ฐ€ํ•˜๋ฉด์„œ ์ฝ”์–ด๋ฐ์ดํ„ฐ์˜ ๋ชจ๋ธ ๋ณ€๊ฒฝ์„ ์œ„ํ•ด Lightweight Migration ํ–ˆ์Šต๋‹ˆ๋‹ค. - Open API์˜ ํ™œ์šฉ - [X] [Open Weather](https://openweathermap.org/current) ์„œ๋น„์Šค๋ฅผ ์‚ฌ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค. - [X] URLSessionDataTask๋ฅผ ํ™œ์šฉํ•ด ๊ธฐ๊ธฐ์˜ ์œ„์น˜๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํ˜„์žฌ ๋‚ ์”จ์ •๋ณด์™€ ๋‚ ์”จ ์•„์ด์ฝ˜์„ ์š”์ฒญํ•ด์„œ ๋ฐ›์•„์˜ต๋‹ˆ๋‹ค. - Core Location ํ”„๋ ˆ์ž„์›Œํฌ์˜ ํ™œ์šฉ - [X] ๊ธฐ๊ธฐ์˜ ์œ„์น˜ ์ •๋ณด์— ๋Œ€ํ•œ ๊ถŒํ•œ์„ ์š”์ฒญํ•ฉ๋‹ˆ๋‹ค. - [X] `CLLocationManager` ๋ฅผ ํ™œ์šฉํ•˜์—ฌ ๊ธฐ๊ธฐ์˜ ์œ„์น˜๋ฅผ ํŒŒ์•…ํ–ˆ์Šต๋‹ˆ๋‹ค. - ์Šคํ† ๋ฆฌ๋ณด๋“œ์—†์ด ์ฝ”๋“œ๋กœ๋งŒ View๊ตฌํ˜„ํ•˜๊ธฐ - [X] ์Šคํ† ๋ฆฌ๋ณด๋“œ๋ฅผ ์‚ญ์ œํ•˜๊ณ  SceneDelegate๋ฅผ ์ˆ˜์ •ํ•ด navigationController๊ฐ€ embeded๋œ View๋ฅผ ๊ตฌํ˜„ํ–ˆ์Šต๋‹ˆ๋‹ค. - swiftLint ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์‚ฌ์šฉํ•˜๊ธฐ - [X] pod ์„ ์ด์šฉํ•ด ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค. - [X] `swiftLint` ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋กœ ์ฝ”๋“œ์ปจ๋ฒค์…˜์„ ํ†ต์ผํ–ˆ์Šต๋‹ˆ๋‹ค. - modernCollectionView๋ฅผ ์ด์šฉํ•œ List๊ตฌํ˜„ - [X] `UICollectionViewCompositionalLayout.list`๋ฅผ ์ด์šฉํ•ด ListView ๊ตฌํ˜„ํ–ˆ์Šต๋‹ˆ๋‹ค. - [X] DiffableDataSource, snapShot์„ ์ดํ•ดํ•˜๊ณ  ์ ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค. - readableContentGuide ์‚ฌ์šฉํ•˜๊ธฐ - [X] app adaptivity ๋ฐ ๊ฐ€๋กœ๋ชจ๋“œ์˜ ๋ ˆ์ด์•„์›ƒ์— readableContentGuide๋ฅผ ์‚ฌ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค. - ์ƒ์†์œผ๋กœ ๋“ฑ๋ก/์ˆ˜์ • ๊ณผ์ •์˜ ๊ณตํ†ต๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ - [X] VC1์™€ ๊ณตํ†ต๊ธฐ๋Šฅ์„ ๊ฐ–๋Š” VC2๋ฅผ Class์ƒ์†์„ ํ†ตํ•ด ๊ตฌํ˜„ํ–ˆ์Šต๋‹ˆ๋‹ค. - ์ปฌ๋ ‰์…˜๋ทฐ ๋ฆฌ์ŠคํŠธ์—์„œ ์Šค์™€์ดํ”„๋ฅผ ์•ก์…˜ ๊ตฌํ˜„ - [X] Cell ์Šค์™€์ดํ”„ ์‹œ, ๋ฐ์ดํ„ฐ๋ฅผ ๊ณต์œ /์‚ญ์ œํ•˜๋Š” ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ–ˆ์Šต๋‹ˆ๋‹ค. - Text View Delegate์˜ ํ™œ์šฉ - [X] ์‚ฌ์šฉ์ž ์ž…๋ ฅ์‹œ PlaceHolder๋ฅผ ์ง€์šฐ๊ธฐ์œ„ํ•ด Text View Delegate๋ฅผ ์‚ฌ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค. --- ## โฐ ํƒ€์ž„๋ผ์ธ | ๐Ÿ•› Step 1| | | :--------: | -------- | | 1 | `SwiftLint` ํ™œ์šฉ | | 2 | DTO๊ตฌํ˜„ - JSON ๋ฐ์ดํ„ฐ์™€ ๋งคํ•‘ํ•  `Diary` ํƒ€์ž… ๊ตฌํ˜„ | | 3 | ์ผ๊ธฐ์žฅ ๋ชฉ๋กํ™”๋ฉด ๊ตฌํ˜„, ์ผ๊ธฐ์žฅ ์˜์—ญํ™”๋ฉด UI๊ตฌํ˜„, readableContentGuide ์ ์šฉ | <details> <summary>[Details - Step1 ํƒ€์ž…๋ณ„ ๊ธฐ๋Šฅ ์„ค๋ช…] </summary> #### 1๏ธโƒฃ DTO - `Diary` ๊ตฌ์กฐ์ฒด - JSONํŒŒ์ผ(sample.json)์„ decodeํ•˜๊ธฐ์œ„ํ•œ DTO(๋ฐ์ดํ„ฐ์ „์†ก๊ฐ์ฒด) ์ž…๋‹ˆ๋‹ค. #### 2๏ธโƒฃ `DiaryListViewController` ํด๋ž˜์Šค - ์ผ๊ธฐ์žฅ ๋ชฉ๋กํ™”๋ฉด์ธ DiaryListView๋ฅผ ๋‹ด๋‹นํ•˜๋Š” ๋ทฐ์ปจํŠธ๋กค๋Ÿฌ์ž…๋‹ˆ๋‹ค. - collectionView(collectionView:didSelectItemAt:) : UICollectionViewDelegateํ”„๋กœํ† ์ฝœ์„ ์ฑ„ํƒํ•˜์—ฌ ๋ชฉ๋ก ์ค‘ ํ•˜๋‚˜์˜ ํ•ญ๋ชฉ์„ ์„ ํƒํ–ˆ์„ ๋•Œ ๋‹ค์Œํ™”๋ฉด์œผ๋กœ ์ „ํ™˜ํ•˜๋Š” ๋ฉ”์„œ๋“œ์ž…๋‹ˆ๋‹ค. #### 3๏ธโƒฃ `DiaryDetailViewController` ํด๋ž˜์Šค - ์ผ๊ธฐ์žฅ ๋ชฉ๋ก ์ค‘ ํ•˜๋‚˜์˜ ์ผ๊ธฐ์ƒ์„ธํ™”๋ฉด์ธ DiaryDetailView๋ฅผ ๋‹ด๋‹นํ•˜๋Š” ๋ทฐ์ปจํŠธ๋กค๋Ÿฌ์ž…๋‹ˆ๋‹ค. -`configureDiary()`๋ฉ”์„œ๋“œ ์ผ๊ธฐ์ƒ์„ธํ™”๋ฉด์— ์ œ๋ชฉ,๋‚ด์šฉ,์ผ์ž ๊ฐ๊ฐ ํ•ญ๋ชฉ๋ณ„ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ”์ธ๋”ฉํ•˜๋Š” ๋ฉ”์„œ๋“œ์ž…๋‹ˆ๋‹ค. - `setupNotification()`๋ฉ”์„œ๋“œ ํ‚ค๋ณด๋“œ๊ฐ€ ๋ณด์ด๊ฑฐ๋‚˜ ์ˆจ๊ฒจ์งˆ ๋•Œ์˜ ๋…ธํ‹ฐํ”ผ์ผ€์ด์…˜์— Observer๋ฅผ ์ถ”๊ฐ€ํ•ด์„œ controlKeyboard() ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค. </details> </br> </br> | ๐Ÿ•› Step 2| | | :--------: | -------- | | 1 | CoreData ๋ชจ๋ธ ์ƒ์„ฑ | | 2 | ์ผ๊ธฐ์žฅ CRUD๊ธฐ๋Šฅ(์ž๋™์ €์žฅ, ์ฝ๊ธฐ, ์ˆ˜์ •, ์‚ญ์ œ) ๊ตฌํ˜„ | | 3 | ์ปฌ๋ ‰์…˜๋ทฐ ๋ฆฌ์ŠคํŠธ์—์„œ ์Šค์™€์ดํ”„๋ฅผ ์•ก์…˜ ๊ตฌํ˜„ | | 4 | Text View Delegate์˜ ํ™œ์šฉ | <details> <summary>[Details - Step2 ํƒ€์ž…๋ณ„ ๊ธฐ๋Šฅ ์„ค๋ช…] </summary> #### 1๏ธโƒฃ `CoreDataManager` ๊ตฌ์กฐ์ฒด - CRUD ๊ธฐ๋Šฅ๋ณ„ ๋ฉ”์„œ๋“œ ๊ตฌํ˜„ - `deleteAllNoDataDiaries()`๋ฉ”์„œ๋“œ ์ƒˆ๋กœ์šด ์ผ๊ธฐ ์ƒ์„ฑ ํ›„ ์•„๋ฌด๊ฒƒ๋„ ์ž…๋ ฅํ•˜์ง€ ์•Š์•˜์„ ๊ฒฝ์šฐ, ์ €์žฅ ํ›„ fetchํ•ด์˜ฌ ๋•Œ ์‚ญ์ œํ•ฉ๋‹ˆ๋‹ค. - `searchDiary(id:)`๋ฉ”์„œ๋“œ ์ˆ˜์ • ์‹œ ํ•ด๋‹น ์ผ๊ธฐ๋งŒ ์ฐพ์•„ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค. #### 2๏ธโƒฃ `RegisterDiaryViewController` ํด๋ž˜์Šค - ์ƒˆ๋กœ์šด ์ผ๊ธฐํ™”๋ฉด์œผ๋กœ ๋‚ด์šฉ์—†์ด PlaceHolder๋งŒ ์ž…๋ ฅ๋˜์–ด์žˆ๋Š” ์ƒํƒœ์˜ `DiaryDetailView`๋ฅผ ๋‹ด๋‹นํ•ฉ๋‹ˆ๋‹ค. #### 3๏ธโƒฃ `DiaryDetailViewController` ํด๋ž˜์Šค - `RegisterDiaryViewController` ํด๋ž˜์Šค๋ฅผ ์ƒ์†๋ฐ›์Šต๋‹ˆ๋‹ค. - ์ด๋ฏธ ์ž‘์„ฑํ•œ ์ผ๊ธฐ๋‚ด์šฉ์ด ์ž…๋ ฅ๋˜์–ด์žˆ๋Š” ์ƒํƒœ์˜ `DiaryDetailView`๋ฅผ ๋‹ด๋‹นํ•ฉ๋‹ˆ๋‹ค. #### 4๏ธโƒฃ `CustomActivityViewController` ํด๋ž˜์Šค - ๊ณต์œ  ์‹œ ๋ทฐ ํ•˜๋‹จ์—์„œ ์˜ฌ๋ผ์˜ค๋Š” `ActivityView`๋ฅผ ๋‹ด๋‹นํ•˜๋Š” ์ปจํŠธ๋กค๋Ÿฌ์ž…๋‹ˆ๋‹ค. </details> </br> </br> | ๐Ÿ•› Step 3| | | :--------: | -------- | | 1 | ์œ„์น˜์ •๋ณด, ๋‚ ์”จ์ •๋ณด ํš๋“์„ ์œ„ํ•œ ํด๋ž˜์Šค ๋‚ด๋ถ€ ๊ตฌํ˜„ | | 2 | URLSession ํ™œ์šฉ ๋ฐ ์„œ๋ฒ„์™€ ํ†ต์‹ ํ•˜๋Š” ๊ด€๋ จ ํด๋ž˜์Šค ๊ตฌํ˜„ | | 3 | CoreData ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ | <details> <summary>[Details - Step3 ํƒ€์ž…๋ณ„ ๊ธฐ๋Šฅ ์„ค๋ช…] </summary> #### 1๏ธโƒฃ `DiaryInfo` DTO - ์ฝ”์–ด๋ฐ์ดํ„ฐ์— ๋‹ค์ด์–ด๋ฆฌ ๋‚ด์šฉ์„ ์ „๋‹ฌํ•ด์ฃผ๋Š” ๋ฐ์ดํ„ฐ์ „์†ก๊ฐ์ฒด ์ž…๋‹ˆ๋‹ค. #### 2๏ธโƒฃ `WeatherInfo` DTO - ๋‚ ์”จAPI์— ์žˆ๋Š” json ์ค‘์ฒฉํƒ€์ž…๋ฐ์ดํ„ฐ๋ฅผ ์ „์—ญํƒ€์ž…์œผ๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ ์ €์žฅํ•˜๊ณ , `DiaryInfo`ํƒ€์ž…์˜ ์†์„ฑ ์ค‘ ํ•˜๋‚˜์˜ ํƒ€์ž…์„ ๋‹ด๋‹นํ•˜๋Š” ๊ตฌ์กฐ์ฒด์ž…๋‹ˆ๋‹ค. #### 3๏ธโƒฃ `WeatherManager` ํด๋ž˜์Šค - fetchWeatherInfo(completion:) : ์„œ๋ฒ„๋กœ๋ถ€ํ„ฐ ๋‚ ์”จ์ •๋ณด๋ฅผ ๋ฐ›์•„์˜ค๊ธฐ์œ„ํ•ด URL์„ ๋งŒ๋“ค๊ณ , `URLSessionProvider()`๋ฅผ ํ†ตํ•ด ๋ฐ์ดํ„ฐ๋ฅผ ์–ป์Šต๋‹ˆ๋‹ค. - fetchWeatherIcon(icon:,completion:) : ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ๋ฐ›์€ `icon`์˜ ๋ฌธ์ž์—ด์— ํ•ด๋‹นํ•˜๋Š” ๋‚ ์”จ์•„์ด์ฝ˜์ด ์œ„์น˜ํ•œ URL์„ ์ด์šฉํ•˜์—ฌ `URLSessionProvider()`๋กœ๋ถ€ํ„ฐ ์ด๋ฏธ์ง€๋ฅผ ์–ป์Šต๋‹ˆ๋‹ค. #### 4๏ธโƒฃ `WeatherURL` ์—ด๊ฑฐํ˜• - ์œ„์น˜์ •๋ณด๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ๋‚ ์”จ์— ๋งž๋Š” ๋‚ ์”จ์•„์ด์ฝ˜์„ ๊ฐ€์ ธ์˜ฌ ๋•Œ ํ•ด๋‹น ์•„์ด์ฝ˜์ด ์œ„์น˜ํ•œ URL์„ ์ƒ์„ฑํ•˜๋Š” ์—ด๊ฑฐํ˜•์ž…๋‹ˆ๋‹ค. #### 5๏ธโƒฃ `LocationManager` ํด๋ž˜์Šค - ์œ„์น˜์ •๋ณด ์ ‘๊ทผ์„ ๋‹ด๋‹นํ•˜๋Š” ํด๋ž˜์Šค์ž…๋‹ˆ๋‹ค. ์œ„์น˜ ์ •ํ™•๋„๊ฐ€ ์ง€์ •๋œ ๊ฐ์ฒด์ธ `manager`๋ฅผ `CLLocationManager`ํƒ€์ž…์œผ๋กœ ์ƒ์„ฑํ•˜์—ฌ ๊ด€๋ จ๋œ ์ผ๋“ค์„ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค. - ์œ„์น˜๊ฐ’์„ ๋‹ด์€ `location` ๋ณ€์ˆ˜๋Š” ์œ„๋„์™€ ๊ฒฝ๋„๋กœ ๋ฐ›์•„์˜ค๋ฏ€๋กœ ํŠœํ”Œํƒ€์ž…์œผ๋กœ ์ •์˜ํ•˜์˜€์Šต๋‹ˆ๋‹ค. - fetchLocation() : ์‚ฌ์šฉ์ž์˜ ํ˜„์žฌ์œ„์น˜ ํš๋“์€ ๋ฐฐํ„ฐ๋ฆฌํšจ์œจ์„ ์œ„ํ•ด `requestLocation()`๋กœ ํ•œ๋ฒˆ๋งŒ ์œ„์น˜๋ฅผ ์š”์ฒญํ•ฉ๋‹ˆ๋‹ค. - CLLocationManagerDelegate ๊ด€๋ จ ๋ฉ”์„œ๋“œ - locationManagerDidChangeAuthorization(manager:) : ์•ฑ์— ๋Œ€ํ•œ ์œ„์น˜ ์ ‘๊ทผ๊ถŒํ•œ ์„ค์ •์ด ๋ณ€๊ฒฝ๋˜๋ฉด ํ˜ธ์ถœ๋ฉ๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž์˜ ์„ ํƒ์— ๋”ฐ๋ผ ์œ„์น˜ ์ ‘๊ทผ๊ถŒํ•œ์„ ํš๋“ํ•˜๊ฑฐ๋‚˜ ๊ฒฐ์ •ํ•ด๋‹ฌ๋ผ๋Š” ์š”์ฒญ์„ ๋ณด๋ƒ…๋‹ˆ๋‹ค. - locationManager(manager:didUpdateLocations:) : ์‚ฌ์šฉ์ž์˜ ์œ„์น˜๋ฅผ ์„ฑ๊ณต์ ์œผ๋กœ ๊ฐ€์ ธ์™”์„ ๊ฒฝ์šฐ ํ˜ธ์ถœ๋ฉ๋‹ˆ๋‹ค. - locationManager(manager:didFailWithError:) : ์‚ฌ์šฉ์ž์˜ ์œ„์น˜๋ฅผ ๊ฐ€์ ธ์˜ค์ง€ ๋ชปํ–ˆ์„ ๊ฒฝ์šฐ ํ˜ธ์ถœ๋ฉ๋‹ˆ๋‹ค. #### 6๏ธโƒฃ `URLSessionProvider` ๊ตฌ์กฐ์ฒด - `URLSession`์„ ์ด์šฉํ•˜์—ฌ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ๋ฐ›์€ `url`์— ์žˆ๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„์˜ต๋‹ˆ๋‹ค. </details> </br> </br> --- ## ๐Ÿ’ญ ๊ณ ๋ฏผํ•œ ๋ถ€๋ถ„ ### 1๏ธโƒฃ ํ‚ค๋ณด๋“œ๊ฐ€ ๋‚˜ํƒ€๋‚˜๊ณ  ์‚ฌ๋ผ์งˆ ๋•Œ, ์ปจํ…์ธ ๋ฅผ ๊ฐ€๋ฆฌ์ง€์•Š๋„๋ก ํ•˜๋Š” ๋ฐฉ๋ฒ• - 1. **ScrollView ์ด์šฉ** - ํ‚ค๋ณด๋“œ๊ฐ€ ๋‚˜ํƒ€๋‚  ๋•Œ, ์ข์•„์ง„ ํ™”๋ฉด์œผ๋กœ๋„ ์Šคํฌ๋กค ํ•ด์„œ ๊ฐ€๋ ค์ง„ ํ™”๋ฉด์„ ๋ณผ ์ˆ˜ ์žˆ๋„๋ก `ScrollView`์— ์ปจํ…์ธ ๋“ค์„ ๋‹ด์•˜์Šต๋‹ˆ๋‹ค. - 2. **ScrollView์˜ inset, offset ์กฐ์ •** - ํ‚ค๋ณด๋“œ๊ฐ€ ๋‚˜ํƒ€๋‚˜๊ณ  ํ™”๋ฉด์„ ์Šคํฌ๋กค ํ•  ๋•Œ ์ปจํ…์ธ ์˜ ๊ฐ€์žฅ ๋ฐ‘๋ถ€๋ถ„์ด ํ‚ค๋ณด๋“œ ๋’ค์— ๊ฐ€๋ ค์ง€๋Š” ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด, ์Šคํฌ๋กค๋ทฐ๊ฐ€ ๋‚˜ํƒ€๋‚  ๋•Œ์˜ Notification์„ ๋ฐ›์•„ ScrollView์˜ Bottom๋ฐฉํ–ฅ์œผ๋กœ ํ‚ค๋ณด๋“œ ๋†’์ด๋งŒํผ์˜ `Inset`์„ ๋”ํ•˜๋„๋ก ํ–ˆ์Šต๋‹ˆ๋‹ค. ๋”๋ถˆ์–ด ์‚ฌ์šฉ์ž๊ฐ€ ๋ณด๊ณ ์žˆ๋˜ ํ™”๋ฉด์„ ์œ ์ง€ํ•˜๊ธฐ์œ„ํ•ด `offset.y`๋„ ํ‚ค๋ณด๋“œ์˜ ๋†’์ด๋งŒํผ ๋นผ์ค๋‹ˆ๋‹ค. - ํ‚ค๋ณด๋“œ๊ฐ€ ์‚ฌ๋ผ์งˆ ๋•Œ๋Š” ๋ฐ˜๋Œ€๋กœ `Inset`์„ ์‚ญ์ œํ•ฉ๋‹ˆ๋‹ค. - 3. **์ž…๋ ฅ์ด ๋๋‚˜๊ณ  ํ‚ค๋ณด๋“œ๋ฅผ ๋‚ด๋ฆฌ๊ธฐ์œ„ํ•œ `keyboardDismissMode` ์†์„ฑ ์‚ฌ์šฉ** - ์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅ์„ ๋๋‚ด๊ณ  ํ‚ค๋ณด๋“œ๋ฅผ ๋‚ด๋ฆฌ๋Š” ๋ฐฉ๋ฒ•์œผ๋กœ UIScrollView์˜ `keyboardDismissMode` ์†์„ฑ์„ ์‚ฌ์šฉํ•ด ์‚ฌ์šฉ์ž๊ฐ€ ์Šคํฌ๋กคํ•˜์—ฌ ํ‚ค๋ณด๋“œ๋ฅผ ๋‹ค์‹œ ๋‚ด๋ฆด ์ˆ˜ ์žˆ๋„๋ก ๊ตฌํ˜„ํ–ˆ์Šต๋‹ˆ๋‹ค. ### 2๏ธโƒฃ ์ƒ์†์œผ๋กœ ๊ณตํ†ต๊ธฐ๋Šฅ ๊ตฌํ˜„์‹œ, ์ฃผ์š” ํ”„๋กœํผํ‹ฐ์— ์ ‘๊ทผ์ œ์–ด(private)๋ฅผ ์„ค์ •ํ•ด์ฃผ๊ธฐ ์œ„ํ•ด ์ด๋‹ˆ์…œ๋ผ์ด์ €์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐˆ์•„๋ผ์šฐ๋Š” ๋กœ์ง ๊ตฌํ˜„ - `RegisterDiaryViewController`(๋‹ค์ด์–ด๋ฆฌ ๋“ฑ๋กํ™”๋ฉด VC)๋ฅผ ์ƒ์†๋ฐ›๋Š” `DiaryDetailViewController`(๋‹ค์ด์–ด๋ฆฌ ์ˆ˜์ •ํ™”๋ฉด VC)๋ฅผ ๊ตฌํ˜„ํ•˜๋ฉด์„œ ํ•„์ˆ˜๋กœ `private`์œผ๋กœ ์„ค์ •ํ•ด์ฃผ์–ด์•ผ ํ•  ํ”„๋กœํผํ‹ฐ๋“ค์— ์ ‘๊ทผ์ œ์–ด๋ฅผ ์„ค์ •ํ•  ์ˆ˜ ์—†๋Š” ๋ฌธ์ œ๊ฐ€ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. VC ๋‘ ๊ฐœ๊ฐ€ ๋˜‘๊ฐ™์ด `diaryPageView`, `diaryPage` ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ–๊ณ ์žˆ๋Š”๋ฐ ์™ธ๋ถ€์—์„œ ์ ‘๊ทผํ•  ์ˆ˜ ์—†๊ฒŒ `Private`์„ค์ •์„ ํ•ด์ฃผ์–ด์•ผ ํ–ˆ์Šต๋‹ˆ๋‹ค. - ์ƒ์†๋ฐ›์€ ํ”„๋กœํผํ‹ฐ๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ  ์ƒˆ๋กœ์šด ํ”„๋กœํผํ‹ฐ๋ฅผ ์ƒ์„ฑํ•ด์„œ ์‚ฌ์šฉํ•˜๊ฒŒ๋˜๋ฉด, ์ด ํ”„๋กœํผํ‹ฐ๋“ค์„ ์‚ฌ์šฉํ•˜๋Š” ๋ฉ”์„œ๋“œ๋“ค์„ ๋ชจ๋‘ `override`ํ•ด์„œ ์ƒˆ๋กœ ์ƒ์„ฑํ•œ ํ”„๋กœํผํ‹ฐ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ๋ชจ๋‘ `override`ํ›„ ์žฌ๊ตฌํ˜„ ํ•ด์•ผ ํ–ˆ์Šต๋‹ˆ๋‹ค. - ์ด๋‹ˆ์…œ๋ผ์ด์ €์—์„œ ํ•ด๋‹ต์„ ์–ป์—ˆ์Šต๋‹ˆ๋‹ค. ์ด๋‹ˆ์…œ๋ผ์ด์ €์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐˆ์•„๋ผ์šฐ๋Š” ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜๋‹ˆ ํ”„๋กœํผํ‹ฐ๋Š” ์ธ์Šคํ„ด์Šค์ƒ์„ฑ์‹œ ๋ฐ”๋€Œ๊ธฐ ๋•Œ๋ฌธ์— Private ์ ‘๊ทผ์ œ์–ด๋ฅผ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๊ณ , ์ƒ์†๋ฐ›์€ ๋ฉ”์„œ๋“œ๋“ค์€ override์—†์ด ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ### 3๏ธโƒฃ ๋ฐ์ดํ„ฐ๊ฐ€ ์—†๋Š” ํŽ˜์ด์ง€๋Š” ์ž๋™ ์‚ญ์ œ๋˜๋Š” ๋กœ์ง - ์ฒ˜์Œ์—” ๋‹ค์ด์–ด๋ฆฌ ๋“ฑ๋ก ์‹œ ์ž‘์„ฑ๋‚ด์šฉ์ด ์žˆ๋Š”์ง€ ํ™•์ธํ•œ ํ›„ ์ €์žฅ์„ ๊ฒฐ์ •ํ•˜๋Š” ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜๋ คํ–ˆ๋Š”๋ฐ, ์ €์žฅํ• ๋•Œ๋„ ํ™•์ธํ•ด์•ผํ•˜๊ณ , ํ•œ๊ธ€์ž๋ฅผ ์จ์„œ ์ด๋ฏธ ์ €์žฅ์ด ๋œ ๋ฐ์ดํ„ฐ๋“ค์— ๋Œ€ํ•ด์„œ๋Š” ์—…๋ฐ์ดํŠธ์‹œ ํ™•์ธ ํ›„ ์‚ญ์ œํ•ด์•ผํ•˜๊ณ , ๋“ฑ๋กํ™”๋ฉด ๋ฟ ์•„๋‹ˆ๋ผ ์ด๋ฏธ ์ €์žฅ๋œ ๋‹ค์ด์–ด๋ฆฌ๋ฅผ ๋ณด๋Š” ํ™”๋ฉด์—์„œ๋„ ๋‚ด์šฉ์„ ๋ชจ๋‘ ์ง€์šฐ๋ฉด ์—…๋ฐ์ดํŠธ๊ฐ€ ์•„๋‹Œ ์‚ญ์ œ๋ฅผ ํ•ด์•ผํ•ด์•ผํ•˜๋Š” ๋ฒˆ๊ฑฐ๋กœ์›€์ด ์žˆ์–ด ์ผ๋‹จ ๋ฐ์ดํ„ฐ๊ฐ€ ์—†๋Š” ํŽ˜์ด์ง€๋„ ์ž๋™์ €์žฅ ๋œ ํ›„ `fetch`ํ•ด ์˜ฌ ๋•Œ(์ €์žฅ๋œ ๋ฆฌ์ŠคํŠธ๋ฅผ ๋ณด์—ฌ์ค„ ๋•Œ) ๋นˆ ๊ฐ’์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์ž์ฒด์ ์œผ๋กœ ์‚ญ์ œํ•œ ํ›„ `fetch`ํ•˜๋Š” ๋กœ์ง์œผ๋กœ ๊ตฌํ˜„ํ–ˆ์Šต๋‹ˆ๋‹ค. ### 4๏ธโƒฃ ๋””์ฝ”๋”ฉ์— ํ•„์š”ํ•œ DTOํƒ€์ž… ๊ตฌํ˜„์—์„œ ์ค‘์ฒฉํƒ€์ž… ์—†์ด ๊ตฌํ˜„ํ•˜๊ธฐ <img width = 600, src = "https://i.imgur.com/hqcn0qA.png"> - [Open Weather](https://openweathermap.org/current) ์„œ๋น„์Šค์— ์š”์ฒญํ•ด ๊ฐ€์ ธ์˜จ ํ˜„์žฌ ๋‚ ์”จ์ •๋ณด๋ฅผ ๋‹ด์€ JSON๋ฐ์ดํ„ฐ ์ค‘ ์‹ค์ œ๋กœ ํ•„์š”ํ•œ ์ •๋ณด๋Š” ์‚ฌ์ง„์— ํ‘œ์‹œํ•œ `weather` ๋‚ด๋ถ€์˜ `main`, `icon` ๋ฐ์ดํ„ฐ ์ž…๋‹ˆ๋‹ค. - ๋””์ฝ”๋”ฉ์„ ์œ„ํ•ด DTO๋ฅผ ๋งŒ๋“ค๋•Œ, ์ฒ˜์Œ์—๋Š” ์•„๋ž˜์™€๊ฐ™์ด `WeatherInfo` ํƒ€์ž…์—์„œ `Weather`ํƒ€์ž…์„ ์ค‘์ฒฉ์œผ๋กœ ๊ฐ–๊ณ , ์ด `Weather`ํƒ€์ž…์ด `main`, `icon` ์„ ๊ฐ–๋Š” ํ˜•์‹์œผ๋กœ ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค. ```swift // ์ค‘์ฒฉํƒ€์ž…์„ ์‚ฌ์šฉํ•œ DTO struct WeatherInfo: Decodable { struct Weather: Decodable { let main: String let icon: String } let weather: Weather } ``` - ํ•˜์ง€๋งŒ ์˜๋ฏธ์—†๋Š” ์ค‘์ฒฉ์ธ ๊ฒƒ ๊ฐ™์•„ ์ค‘์ฒฉํƒ€์ž…์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ , `CodingKey`์™€, Decodable์˜ init๋‚ด๋ถ€์—์„œ `KeyedDecodingContainer` ๋ฅผ ํ™œ์šฉํ•ด `WeatherInfo`ํƒ€์ž…์ด `main`, `icon`์„ ๊ฐ–๋„๋ก ํ–ˆ์Šต๋‹ˆ๋‹ค. ```swift // ์ค‘์ฒฉํƒ€์ž…์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” DTO struct WeatherInfo { let main: String let icon: String } extension WeatherInfo: Decodable { enum CodingKeys: String, CodingKey { case main, icon, weather } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) var weatherContainer = try container.nestedUnkeyedContainer(forKey: .weather) let nestedContainer = try weatherContainer.nestedContainer(keyedBy: CodingKeys.self) main = try nestedContainer.decode(String.self, forKey: .main) icon = try nestedContainer.decode(String.self, forKey: .icon) } } ``` --- ## ๐Ÿš€ ํŠธ๋Ÿฌ๋ธ” ์ŠˆํŒ… ### 1๏ธโƒฃ DiffableDataSource์‚ฌ์šฉํ•  ๋•Œ, Value๊ฐ’์ด ๋˜‘๊ฐ™์€ ๋ฐ์ดํ„ฐ๋กœ ์ธํ•œ ๋ฌธ์ œ ํ•ด๊ฒฐ - ํ”„๋กœ์ ํŠธ์— ํฌํ•จ๋œ ๊ฒฌ๋ณธ JSON ๋ฐ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•ด List Cell์„ ๊ตฌ์„ฑํ•  ๋•Œ, ๊ฐ’์ด ๊ฐ™์€ ๋ฐ์ดํ„ฐ๊ฐ€์žˆ์–ด ์ •์ƒ์ž‘๋™ ๋˜์ง€์•Š๋Š” ๋ฌธ์ œ๊ฐ€ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. - ์›์ธ: Diffable datasource๋ฅผ ์‚ฌ์šฉํ•˜๋ ค๋ฉด `ItemIdentifier`๊ฐ€ ๊ฐ๊ฐ ๊ณ ์œ ํ•œ Hash๊ฐ’์„ ๊ฐ–๊ณ  ์žˆ์–ด์•ผํ•˜๋Š”๋ฐ ์•„๋ž˜ ์บก์ณ ํ™”๋ฉด ์ฒ˜๋Ÿผ value๊ฐ€ ์™„์ „ํžˆ ๊ฐ™์„ ๋•Œ๋Š” ๋‘ ๊ฐœ๊ฐ€ ๊ฐ™์€ Hash๊ฐ’์„ ๊ฐ–๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ![](https://i.imgur.com/PpWdKev.jpg) </br> - **โœ… ์ˆ˜์ •: UUID ์‚ฌ์šฉ** - DTO์— `UUID`๋ฅผ ์ƒ์„ฑํ•ด ๊ฐ value๋“ค์ด ๊ฐ๊ฐ ๊ณ ์œ ํ•œ Hash๊ฐ’์„ ๊ฐ–๋„๋ก ํ–ˆ์Šต๋‹ˆ๋‹ค. ```swift struct Diary: Decodable, Hashable { let title: String let body: String let createdAt: Double let id = UUID() // ๐Ÿ‘ˆ // ... } ``` ### 2๏ธโƒฃ ์ผ๊ธฐ์žฅ ๋ชฉ๋ก์˜ ์…€ ๋†’์ด ์กฐ์ ˆ - ์˜คํ† ๋ ˆ์ด์•„์›ƒ ๋ฌธ์ œ ํ•ด๊ฒฐ - `DiaryCell`์— `configureDiaryCellLayout()`๋ฉ”์„œ๋“œ์—์„œ AutoLayout Constraints ์„ค์ • ์‹œ ์ผ๊ธฐ ์ œ๋ชฉ๊ณผ ์ž‘์„ฑ์ผ ๋“ฑ์˜ Label์„ ๋‹ด์€ `totalStackView`์˜ `Constraints`๋ฅผ `self`๋ฅผ ๊ธฐ์ค€์œผ๋กœ ํ–ˆ์„ ๋•Œ ์˜๋„ํ•œ๋Œ€๋กœ ์…€๋†’์ด๊ฐ€ ์กฐ์ ˆ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. | ์ˆ˜์ • ์ „ | ์ˆ˜์ • ํ›„ | | :--------: | :--------: | | <img height = 400 src = "https://i.imgur.com/I73XrwP.png"> | <img height = 400 src = "https://i.imgur.com/S6m4APA.png"> | - **โœ… ์ˆ˜์ •: `LayoutConstraint`์„ `contentView`์— ๊ฑธ์–ด์ฃผ๊ธฐ** - `LayoutConstraint`์„ `self(DiaryCell)`์ด ์•„๋‹Œ `contentView`๋ฅผ ๊ธฐ์ค€์œผ๋กœ `topAnchor`์™€ `bottomAnchor`์— `constant`๊ฐ’์„ ์ฃผ์–ด ํ•ด๊ฒฐํ•˜์˜€์Šต๋‹ˆ๋‹ค. ### 3๏ธโƒฃ ์ผ๊ธฐ์žฅ ๋ชฉ๋ก์˜ ์…€ ๋†’์ด ์กฐ์ ˆ - ํด๋ฆญ ์‹œ ์ •์ƒ๋†’์ด๊ฐ€ ๋˜๋Š” ๋ฌธ์ œ ํ•ด๊ฒฐ - ๋‹ค์ด์–ด๋ฆฌ ๋ฆฌ์ŠคํŠธํ™”๋ฉด์—์„œ `DiaryCell`์˜ ๋†’์ด๊ฐ€ ์•„๋ž˜GIF์ฒ˜๋Ÿผ ์ž‘์•˜๋‹ค๊ฐ€ ํด๋ฆญ ์‹œ ์ •์ƒ๋†’์ด๋กœ ๋Œ์•„์˜ค๋Š” ๋ฌธ์ œ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. - ์›์ธ์€ ์…€์˜ ๋ ˆ์ด์•„์›ƒ์„ ์žก์•„์ฃผ๋Š” `configureDiaryCellLayout()` ๋ฉ”์„œ๋“œ๊ฐ€ Cell์˜ ์ œ๋ชฉ, ๋‚ ์”จ์•„์ด์ฝ˜, ๋‚ด์šฉ์„ ๋‹ด์•„์ฃผ๋Š” `configureCell()` ๋ฉ”์„œ๋“œ ๋‚ด๋ถ€์—์„œ ํ˜ธ์ถœ์ด ๋˜๋Š”๋ฐ, ์ด `configureCell()` ๋ฉ”์„œ๋“œ์˜ ํ˜ธ์ถœ ์‹œ์ ์ด ์“ฐ๋ ˆ๋“œ๊ด€๋ จ ๋ฌธ์ œ๋กœ ๊ฐ€์žฅ ๋งˆ์ง€๋ง‰์— ํ˜ธ์ถœ๋˜๋Š” ๋ฌธ์ œ์˜€์Šต๋‹ˆ๋‹ค. - ์„œ๋ฒ„์—์„œ ๊ฐ€์ ธ์˜ค๋Š” ๋‚ ์”จ์•„์ด์ฝ˜์„ ๋‹ด์•„์ฃผ๊ธฐ ์œ„ํ•ด์„œ URLSessionDataTask๋ฅผ ํ™œ์šฉํ•  ๋•Œ ๋‹ค๋ฅธ์“ฐ๋ ˆ๋“œ์—์„œ ์ž‘์—…์ด ์ง„ํ–‰๋˜๊ณ , ์ž‘์—…์ด ๋๋‚œ ํ›„์—” ๊ฐ€์ ธ์˜จ ์•„์ด์ฝ˜์„ ๊ฐ–๊ณ  `DispatchQueue.main.async`๋กœ ๋ณด๋‚ด main์“ฐ๋ ˆ๋“œ์—์„œ `configureCell()`๋ฉ”์„œ๋“œ๊ฐ€ ์ง„ํ–‰๋˜๋„๋ก ํ–ˆ์—ˆ์Šต๋‹ˆ๋‹ค. ์ด ๋•Œ ๋ฌธ์ œ๋Š”, ๋‹ค์‹œ main์œผ๋กœ ๋ณด๋‚ด์ง„ ์ž‘์—…์ด ์›๋ž˜ main์˜ ๋ชจ๋“  ์ž‘์—…์ด ๋๋‚œ ํ›„์— ์ง„ํ–‰์ด ๋˜๋ฏ€๋กœ ์ปฌ๋ ‰์…˜๋ทฐ๋ฆฌ์ŠคํŠธ๊ฐ€ ๋ชจ๋‘ ๊ทธ๋ ค์ง„ ํ›„, `configureCell()`์ด ๋ถˆ๋ฆฌ๊ณ , ์—ฌ๊ธฐ์— ํฌํ•จ๋œ ๋ ˆ์ด์•„์›ƒ์„ ์žก์•„์ฃผ๋Š”`configureDiaryCellLayout()`๋„ ๋‚˜์ค‘์— ๋ถˆ๋ ค์ง€๋Š”๊ฒŒ ์›์ธ์ด์—ˆ์Šต๋‹ˆ๋‹ค. | ์ˆ˜์ • ์ „| ์ˆ˜์ • ํ›„ | | :--------: | :--------: | | <img height = 400 src = "https://i.imgur.com/OewIbcp.gif"> | <img height = 400 src = "https://i.imgur.com/zeNeua7.gif"> | - **โœ… ์ˆ˜์ •: ๋‚ ์”จ์•„์ด์ฝ˜์„ ๋‹ด์•„์ฃผ๋Š” ์ž‘์—…๋งŒ `DispatchQueue.main.async`๋กœ ์ฒ˜๋ฆฌ** - `configureCell()`,`configureDiaryCellLayout()` ๋ฉ”์„œ๋“œ๋Š” main์“ฐ๋ ˆ๋“œ์—์„œ ํ˜ธ์ถœํ•ด์„œ ๋จผ์ € ๋ ˆ์ด์•„์›ƒ์„์„ ์žก๊ณ  ์ œ๋ชฉ๊ณผ ๋‚ด์šฉ๋งŒ ๋‹ด๊ฒจ์žˆ๊ฒŒ ๋˜๊ณ , ์„œ๋ฒ„ ํ†ต์‹ ์„ ์œ„ํ•ด ๋‹ค๋ฅธ ์“ฐ๋ ˆ๋“œ์—์„œ ์ž‘์—…์ด ํ•„์š”ํ•œ ๋‚ ์”จ์•„์ด์ฝ˜ fetch ์ž‘์—…๋งŒ ๊ธฐ๋Šฅ์„ ๋ถ„๋ฆฌํ•ด ์ž‘์—…์ด ๋๋‚˜๋ฉด ๋‹ค์‹œ main์“ฐ๋ ˆ๋“œ๋กœ ๋ณด๋‚ด๋Š” `DispatchQueue.main.async`๋กœ ์ฒ˜๋ฆฌํ•ด ์ •์ƒ์ ์œผ๋กœ ๋ ˆ์ด์•„์›ƒ์ด ์žกํ˜€์žˆ๋Š” Cell์— icon์„ ๋„ฃ์„ ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ### 4๏ธโƒฃ ๋‹ค์ด์–ด๋ฆฌ ์ €์žฅ ์‹œ 2๊ฐœ์”ฉ ์ €์žฅ๋˜๋Š” ๋ฌธ์ œ ํ•ด๊ฒฐ - ๋‹ค์ด์–ด๋ฆฌ ํ•œ ๊ฐœ ์ €์žฅ ์‹œ ๋‘ ๊ฐœ๊ฐ€ ์ €์žฅ๋˜์–ด ๋ชฉ๋ก์— ๋‚˜์˜ค๋Š” ๋ฌธ์ œ๊ฐ€ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ํ•˜๋‚˜๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•œ ๋‚ด์šฉ์˜ ๋‹ค์ด์–ด๋ฆฌ์ด๊ณ , ๋‹ค๋ฅธ ํ•˜๋‚˜๋Š” ๋‚ด์šฉ์—†๋Š” ๋‹ค์ด์–ด๋ฆฌ๊ฐ€ ์ƒ์„ฑ๋˜์–ด ๋ชฉ๋ก์— ๋ณด์˜€์Šต๋‹ˆ๋‹ค. - ์›์ธ์€ CoreData์—์„œ `Weather`์™€ `Diary` ์—”ํ‹ฐํ‹ฐ์˜ Relationship์„ค์ • ์ค‘์— ์˜ `Parent Entity`๋กœ `Diary`๋ฅผ ์„ค์ •ํ•ด ๋†“์€๊ฒŒ ๋ฌธ์ œ์˜€์Šต๋‹ˆ๋‹ค. - **โœ… ์ˆ˜์ •: No Parent Entity ์„ค์ •** - `Weather` ์†์„ฑ ์„ค์ •์„ Xcode ์šฐ์ธก์— ์žˆ๋Š” `Data Model Inspector` - `Entity` - `Parent Entity`์˜ต์…˜์—์„œ `No Parent Entity`๋กœ ์„ค์ •ํ•˜์—ฌ ํ•ด๊ฒฐํ•˜์˜€์Šต๋‹ˆ๋‹ค. ### 5๏ธโƒฃ TextView PlaceHolder ์„ค์ • ๋ฐฉ๋ฒ• - PlaceHolder๋ฅผ TextView.text ์†์„ฑ์œผ๋กœ "์ผ๊ธฐ ์ œ๋ชฉ", "์ผ๊ธฐ ๋‚ด์šฉ" ๊ณผ๊ฐ™์€ PlaceHolder๋ฅผ ๋งŒ๋“ค๊ณ  `textViewDidBeginEditing`๋ฉ”์„œ๋“œ๋กœ ๊ฐ’์ด ๋ฐ”๋€Œ์—ˆ์„ ๋•Œ, PlaceHolder๊ฐ€ ์ง€์›Œ์ง€๋Š” ๋กœ์ง์„ ๊ตฌํ˜„ํ–ˆ์„ ๋•Œ ์•„๋ž˜์™€ ๊ฐ™์€ ๋ฌธ์ œ๊ฐ€ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. - ์ฝ”์–ด๋ฐ์ดํ„ฐ์— ๋ฐ์ดํ„ฐ ์ €์žฅ ์‹œ ์ž…๋ ฅ๊ฐ’์ด ์—†์„ ๋•Œ๋„ ์ž…๋ ฅ๋˜์–ด์žˆ๋Š” PlaceHolder์ธ "์ผ๊ธฐ ์ œ๋ชฉ", "์ผ๊ธฐ ๋‚ด์šฉ"์„ Text๋กœ ์ธ์‹ํ•ด ์ €์žฅ๋˜๋Š” ๋ฌธ์ œ๊ฐ€ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. - ํ™”๋ฉด์ „ํ™˜ ์‹œ ํ‚ค๋ณด๋“œ๋ฅผ ๋ฐ”๋กœ ๋„์šฐ๊ธฐ ์œ„ํ•ด TitleTextView๋ฅผ FirstResponder๋กœ ์ง€์ •ํ•ด๋†“์œผ๋‹ˆ `textViewDidBeginEditing` ๋ฉ”์„œ๋“œ๊ฐ€ ํ˜ธ์ถœ๋˜์–ด PlaceHolder๊ฐ€ ๋ณด์ด์ง€ ์•Š๋Š” ๋ฌธ์ œ๊ฐ€ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. - `textViewDidBeginEditing`๋Œ€์‹ , `textViewDidChange`๋กœ ๊ฐ’์ด ํ•˜๋‚˜๋ผ๋„ ์ ํž ๋•Œ PlaceHolder๊ฐ€ ์ œ๊ฑฐ๋˜๋Š” ๋กœ์ง์œผ๋กœ ๋ณ€๊ฒฝํ•˜๋ฉด PlaceHolder๋Š” ํ™”๋ฉด์— ๋ณด์ด์ง€๋งŒ ํƒ€์ž์ปค์„œ๊ฐ€ PlaceHolder ๋’ค์ชฝ์— ์œ„์น˜ํ•˜๋Š” ๋ฌธ์ œ๊ฐ€ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. - **โœ… ์ˆ˜์ •: Label์‚ฌ์šฉ** - UILabel์ธ `titlePlaceHolder`์™€ `bodyPlaceHolder`๋ฅผ ๋งŒ๋“ค์–ด TextView์— ์ถ”๊ฐ€ํ•ด ๋ณด์ด๊ฒŒ ํ•˜๊ณ  UITextViewDelegate์˜ `textViewDidChange`๋ฉ”์„œ๋“œ๋กœ ์ถ”๊ฐ€๋œ Label์ด ์‚ญ์ œ๋˜๋„๋ก ์ˆ˜์ •ํ•ด ํ•ด๊ฒฐํ–ˆ์Šต๋‹ˆ๋‹ค. - Label ์‚ฌ์šฉ์‹œ, ์ž…๋ ฅ๊ฐ’์ด ์—†์„ ๋•Œ์— ์ €์žฅ๋˜๋Š” ๋ฐ์ดํ„ฐ์˜ ๊ฐ’๋„ ๋นˆ๊ฐ’์œผ๋กœ ์ •์ƒ ์ €์žฅ๋˜๊ณ , ํ™”๋ฉด์ „ํ™˜์‹œ FirstResponder์—ฌ๋„ PlaceHolder๊ฐ€ ์ž˜ ๋ณด์ด๊ณ , ์ปค์„œ์˜ ์œ„์น˜๋„ ์ •์ƒ์ ์œผ๋กœ(์ œ์ผ ์•ž์ชฝ์—) ์œ„์น˜ํ•ฉ๋‹ˆ๋‹ค. --- ## ๐Ÿ”— ์ฐธ๊ณ  ๋งํฌ [๊ณต์‹๋ฌธ์„œ] - [Implementing Modern Collection Views](https://developer.apple.com/documentation/uikit/views_and_controls/collection_views/implementing_modern_collection_views) - [UICollectionViewDiffableDataSource](https://developer.apple.com/documentation/uikit/uicollectionviewdiffabledatasource/) - [Setting Up a Core Data Stack](https://developer.apple.com/documentation/coredata/setting_up_a_core_data_stack) - [readableContentGuide](https://developer.apple.com/documentation/uikit/uiview/1622644-readablecontentguide/) - [DateFormatter](https://developer.apple.com/documentation/foundation/dateformatter) - [UITextViewDelegate](https://developer.apple.com/documentation/uikit/uitextviewdelegate/) - [UISwipeActionsConfiguration](https://developer.apple.com/documentation/uikit/uiswipeactionsconfiguration/) - [Core Location](https://developer.apple.com/documentation/corelocation) - [๋‚ ์”จ API](https://openweathermap.org/current)