# ๐Ÿ“˜ ์ผ๊ธฐ์žฅ <br> ## ๐Ÿ’พ ํ”„๋กœ์ ํŠธ ์†Œ๊ฐœ >**ํ”„๋กœ์ ํŠธ ๊ธฐ๊ฐ„** : 2022-08-16 ~ 2022-09-02<br> **์†Œ๊ฐœ** : ๊ทธ๋‚  ๊ฒช์€ ์ผ์ด๋‚˜ ์ƒ๊ฐ์ด๋‚˜ ๋А๋‚Œ์„ ์ ๋Š” ์ผ๊ธฐ์žฅ ์•ฑ์ž…๋‹ˆ๋‹ค. <br> **๋ฆฌ๋ทฐ์–ด** : [**์ œ์ดํฌ**](https://github.com/jryoun1) <br> ## ๋ชฉ์ฐจ * [ํŒ€์›](#-ํŒ€์›) * [์‹คํ–‰ ํ™”๋ฉด](#-์‹คํ–‰-ํ™”๋ฉด) * [๊ฐœ๋ฐœ ํ™˜๊ฒฝ](#-๊ฐœ๋ฐœ-ํ™˜๊ฒฝ) * [ํ•ต์‹ฌ ๊ฒฝํ—˜](#-ํ•ต์‹ฌ-๊ฒฝํ—˜) * [ํƒ€์ž„ ๋ผ์ธ](#-ํƒ€์ž„-๋ผ์ธ) * [week2](#week-2) * [week3](#week-3) * [ํด๋” ๊ตฌ์กฐ](#-ํด๋”-๊ตฌ์กฐ) * [๊ตฌํ˜„ ๋‚ด์šฉ](#-๊ตฌํ˜„-๋‚ด์šฉ) * [ํŠธ๋Ÿฌ๋ธ” ์ŠˆํŒ…](#-ํŠธ๋Ÿฌ๋ธ”-์ŠˆํŒ…) * [์ฐธ๊ณ  ๋ฌธ์„œ](#-์ฐธ๊ณ -๋ฌธ์„œ) <br> ## ๐Ÿ‘ฅ ํŒ€์› | [ํ˜„์ด](https://github.com/seohyeon2) | [์˜ˆํ†ค](https://github.com/yeeton37) | |:---:|:---:| |<img src = "https://i.imgur.com/0UjNUFH.jpg" width="200" height="230">|<img src = "https://i.imgur.com/TI2ExtK.jpg" width=200 height = 230>| <br> ## ๐Ÿ“บ ์‹คํ–‰ ํ™”๋ฉด | ๋ฉ”์ธ ํ™”๋ฉด | ์–ผ๋Ÿฟ ํ™”๋ฉด | |:---:|:---:| |<img src = "https://i.imgur.com/XQ0vRmR.gif" width=200 height=400>|<img src = "https://i.imgur.com/9WikuB9.png" width=200 height=400>| | ์ผ๊ธฐ์žฅ ์ถ”๊ฐ€ ํ™”๋ฉด | ์ผ๊ธฐ์žฅ ์ˆ˜์ • ํ™”๋ฉด | |<img src = "https://i.imgur.com/dyWwzfX.gif" width=200 height=400>|<img src = "https://i.imgur.com/3plNOYs.gif" width=200 height=400>| | ์ผ๊ธฐ์žฅ ๊ณต์œ  ํ™”๋ฉด | ์ผ๊ธฐ์žฅ ์‚ญ์ œ ํ™”๋ฉด | |<img src = "https://i.imgur.com/11Cxkam.gif" width=200 height=400>|<img src = "https://i.imgur.com/ziIy5or.gif" width=200 height=400>| <br> ## ๐Ÿ›  ๊ฐœ๋ฐœ ํ™˜๊ฒฝ [![swift](https://img.shields.io/badge/swift-5.6-orange)]() [![xcode](https://img.shields.io/badge/Xcode-13.4.1-blue)]() [![swiftLint](https://img.shields.io/badge/SwiftLint-13.2-green)]() <br> ## ๐Ÿ’ป ํ•ต์‹ฌ ๊ฒฝํ—˜ - [x] ์ฝ”์–ด๋ฐ์ดํ„ฐ ๋ชจ๋ธ ์ƒ์„ฑ - [x] ํ…Œ์ด๋ธ”๋ทฐ์—์„œ ์Šค์™€์ดํ”„๋ฅผ ํ†ตํ•œ ์‚ญ์ œ๊ธฐ๋Šฅ ๊ตฌํ˜„ - [x] CoreData๊ด€๋ จ ๋ฉ”์„œ๋“œ ํ”„๋ ˆ์ž„์›Œํฌํ™” - [x] DiffableDataSource์˜ process - [x] UX๋ฅผ ๊ณ ๋ คํ•œ ์ฝ”๋“œ ์ž‘์„ฑ <br> ## ๐Ÿ•– ํƒ€์ž„ ๋ผ์ธ ### Week 2 - **2022-08-22 (์›”)** - ๋ฐ์ดํ„ฐ ์ „๋‹ฌ ๋ฐฉ์‹ ํ”„๋กœํ† ์ฝœ์„ ์ด์šฉํ•œ ๋ฐฉ์‹์œผ๋กœ ์ˆ˜์ • ๋ฐ ๊ฐœ์ธ ๊ณต๋ถ€ - **2022-08-23 (ํ™”)** - ์ผ๊ธฐ์žฅ STEP 2-1: CoreData Entity ์ƒ์„ฑ - **2022-08-24 (์ˆ˜)** - ์ผ๊ธฐ์žฅ STEP 2-1: CoreData Create์™€ Delete ๊ตฌํ˜„ - **2022-08-25 (๋ชฉ)** - ์ผ๊ธฐ์žฅ STEP 2-2: CoreData Update ๊ตฌํ˜„ ๋ฐ ์ผ๊ธฐ์žฅ ์ž๋™ ์ €์žฅ ๊ตฌํ˜„ - **2022-08-26 (๊ธˆ)** - ์ผ๊ธฐ์žฅ STEP 2-2: Error ๊ตฌํ˜„ ๋ฐ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ง„์ž… ์‹œ ์ž๋™์ €์žฅ ๋˜๋Š” ๊ธฐ๋Šฅ ๊ตฌํ˜„ ### Week 3 - **2022-08-29 (์›”)** - CoreData ๊ด€๋ จ ๋ฉ”์„œ๋“œ ํ”„๋ ˆ์ž„์›Œํฌํ™” ๊ณต๋ถ€ - **2022-08-30 (ํ™”)** - CoreData ๊ด€๋ จ ๋ฉ”์„œ๋“œ ํ”„๋ ˆ์ž„์›Œํฌํ™” ๊ณต๋ถ€ - **2022-08-31 (์ˆ˜)** - diffableDataSource ์ด์Šˆ ํ•ด๊ฒฐ - **2022-09-01 (๋ชฉ)** - ์ผ๊ธฐ์žฅ ์ €์žฅ ์‹œ, ์‹คํŒจํ•˜๋Š” ์ผ€์ด์Šค ์ฒ˜๋ฆฌ - **2022-09-02 (๊ธˆ)** - ํ”ผ๋“œ๋ฐฑ ๋ฐ”ํƒ•์œผ๋กœ ์ „๋ฐ˜์ ์ธ ์ฝ”๋“œ ์ˆ˜์ • <br> ## ๐Ÿ—‚ ํด๋” ๊ตฌ์กฐ ```swift . โ””โ”€โ”€ Diary/ โ”œโ”€โ”€ Diary โ”œโ”€โ”€ info.plist โ”œโ”€โ”€ Resources/ โ”‚ โ”œโ”€โ”€ LaunchScreen โ”‚ โ””โ”€โ”€ Assets โ””โ”€โ”€ Sources/ โ”œโ”€โ”€ AppDelegate โ”œโ”€โ”€ SceneDelegate โ”œโ”€โ”€ Util/ โ”‚ โ”œโ”€โ”€ Common/ โ”‚ โ”‚ โ””โ”€โ”€ Enum/ โ”‚ โ”‚ โ””โ”€โ”€ Section โ”‚ โ”‚ โ””โ”€โ”€ Extension/ โ”‚ โ”‚ โ””โ”€โ”€ UIViewController + extension โ”‚ โ”‚ โ””โ”€โ”€ Error/ โ”‚ โ”‚ โ””โ”€โ”€ CoreDataError โ”‚ โ”‚ โ””โ”€โ”€ Protocol/ โ”‚ โ”‚ โ”œโ”€โ”€ ReuseIdentifying โ”‚ โ”‚ โ”œโ”€โ”€ SendDataDelegate โ”‚ โ”‚ โ””โ”€โ”€ CoreDataProcessing โ”‚ โ””โ”€โ”€ JSONData โ”œโ”€โ”€ Controllers/ โ”‚ โ”œโ”€โ”€ DiaryDetailViewController โ”‚ โ””โ”€โ”€ DiaryListTableViewController โ”œโ”€โ”€ Views/ โ”‚ โ””โ”€โ”€ DiaryTableViewCell โ””โ”€โ”€ Models/ โ”œโ”€โ”€ DiaryContents + CoreDataClass โ”œโ”€โ”€ DiaryContents + CoreDataProperties โ”œโ”€โ”€ DiarySample โ””โ”€โ”€ DataManager ``` <br> ## ๐Ÿ”ง ๊ตฌํ˜„ ๋‚ด์šฉ |๊ตฌํ˜„ ์œ„์น˜(ํŒŒ์ผ๋ช…)|ํ•ต์‹ฌ ๊ตฌํ˜„ ๋‚ด์šฉ| |:--|:--| |`CoreDataError`|`CoreData` ๊ด€๋ จ ์—๋Ÿฌ ํƒ€์ž… ๊ตฌํ˜„| |`ReuseIdentifying`|`Identifier` ๋„ค์ž„์ŠคํŽ˜์ด์Šค ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•œ ํ”„๋กœํ† ์ฝœ ๊ตฌํ˜„| |`SendDataDelegate`| ๋ทฐ์ปจํŠธ๋กค๋Ÿฌ๊ฐ„ ๋ฐ์ดํ„ฐ ์ „๋‹ฌํ•ด์ฃผ๋Š” ํ”„๋กœํ† ์ฝœ ๊ตฌํ˜„| |`CoreDataProcessing`| `CoreData` ๊ด€๋ จ ๋ฉ”์„œ๋“œ ๊ตฌํ˜„| |`DiaryDetailViewController`|์ผ๊ธฐ ์ƒ์„ฑ ๋ฐ ์—…๋ฐ์ดํŠธ ๊ธฐ๋Šฅ, ์•กํ‹ฐ๋น„ํ‹ฐ๋ทฐ, ์•Œ๋ฆผ์ฐฝ ๊ตฌํ˜„| |`DiaryListTableViewController`|์Šค์™€์ดํ”„ ์‹œ ์ผ๊ธฐ ์‚ญ์ œ๋˜๋Š” ๊ธฐ๋Šฅ ๊ตฌํ˜„| |`SceneDelegate`|๋ฐฑ๊ทธ๋ผ์šด ์ง„์ž… ์‹œ ์ž๋™ ์ €์žฅ๋˜๋Š” ๊ธฐ๋Šฅ ๊ตฌํ˜„| |`Diary`|`DiaryContents` Entities ๊ตฌํ˜„| |`DataManager`|`DiffableDataSource`์™€ `DiffableDataSourceSnapshot`์„ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” ์‹ฑ๊ธ€ํ†ค ๊ตฌํ˜„| |`Section`|`DiffableDataSource`์—์„œ ์‚ฌ์šฉํ•  ์„น์…˜ ์—ด๊ฒจํ˜• ์ •์˜| <br> ## ๐Ÿš€ ํŠธ๋Ÿฌ๋ธ” ์ŠˆํŒ… ### 1. DiffableDataSource์˜ dequeueReusableCell ํ˜ธ์ถœ ์ด์Šˆ - ๋ฌธ์ œ ์ƒํ™ฉ - cell์— ๋ณด์—ฌ์ง€๋Š” ๋ฐ์ดํ„ฐ๊ฐ€ ์ˆ˜์ •๋  ๋•Œ๋งˆ๋‹ค, `dequeueReusableCell` ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•ด์•ผ์ง€๋งŒ cell์— ๋ฐ˜์˜๋์Šต๋‹ˆ๋‹ค. - ์ด ๋•Œ๋ฌธ์— `dequeueReusableCell`๋ฉ”์„œ๋“œ๋ฅผ `viewDidLoad` ๊ฐ€ ์•„๋‹Œ `viewWillAppear`๋‚˜ `viewDidAppear` ์— ์œ„์น˜์‹œ์ผœ ํ™”๋ฉด์ด ๋‹ค์‹œ ๋‚˜ํƒ€๋‚  ๋•Œ๋งˆ๋‹ค ์‹คํ–‰ํ•ด์•ผ์ง€๋งŒ cell์ด ์ œ๋Œ€๋กœ ๋ฐ”๋€Œ์—ˆ์Šต๋‹ˆ๋‹ค. - ์˜ˆ์‹œ) `dequeueReusableCell`๋ฉ”์„œ๋“œ๋ฅผ `viewDidLoad` ํ˜ธ์ถœ ์‹œํ‚ฌ ์‹œ, cell์— ์ˆ˜์ •๋œ ๋‚ด์šฉ์ด ๋ฐ˜์˜์•ˆ๋จ <img src = "https://i.imgur.com/4G85SCL.gif" width=200 height=400> - ๋””๋ฒ„๊ทธ ์˜ค๋ฅ˜ ๋‚ด์šฉ > [UIDiffableDataSource] > > Diffable data source detected an attempt to insert or append 1 item identifier that already exists in the snapshot. The existing item identifier will be moved into place instead, but this operation will be more expensive. For best performance, inserted item identifiers should always be unique. Set a symbolic breakpoint on BUG_IN_CLIENT_OF_DIFFABLE_DATA_SOURCE__IDENTIFIER_ALREADY_EXISTS to catch this in the debugger. Item identifier that already exists ... - ์›์ธ - ๋ฐ์ดํ„ฐ ์ˆ˜์ • ์‹œ์—๋„ snapshot์— ๋ณ€๊ฒฝ๋œ ๋ฐ์ดํ„ฐ๋ฅผ ์ถ”๊ฐ€ํ•˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๊ธฐ์กด์— ์ถ”๊ฐ€๋œ ๋ฐ์ดํ„ฐ๋ž‘ ์ถฉ๋Œ์ด ๋‚ฌ์Šต๋‹ˆ๋‹ค. - ์ด ์ถฉ๋Œ๋กœ ์ธํ•ด, snapShot์— ๋ฐ˜์˜์€ ๋˜์—ˆ์ง€๋งŒ, dataSource๋Š” ์ด๋ฏธ cell์— ๋ณด์—ฌ์ค€ ๊ฒƒ์œผ๋กœ ์ธ์‹ํ•˜์—ฌ `dequeueReusableCell` ๋ฉ”์„œ๋“œ๋ฅผ ์ˆ˜๋™์ ์œผ๋กœ ํ˜ธ์ถœํ•ด์ค˜์•ผ๋งŒ ํ–ˆ์Šต๋‹ˆ๋‹ค. - ํ•ด๊ฒฐ ๋ฐฉ์•ˆ - snapshot์— ๋ฐ์ดํ„ฐ๋ฅผ `append`ํ•˜๋Š” ๊ฒฝ์šฐ(์ƒˆ๋กœ์šด ์ผ๊ธฐ ์ƒ์„ฑ)์™€ snapShot์— ๋ฐ์ดํ„ฐ๋ฅผ `reload`(์ผ๊ธฐ ์ƒ์„ฑ ์™ธ) ํ•ด์ฃผ๋Š” ๊ฒฝ์šฐ๋ฅผ ๋‚˜๋ˆ„์–ด์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค. ### 2. UX๋ฅผ ๊ณ ๋ คํ•˜์—ฌ ์…€์— ๋ณด์—ฌ์ง€๋Š” title๊ณผ body ์ˆ˜์ • - ๋ฌธ์ œ ์ƒํ™ฉ - ํ”„๋กœ์ ํŠธ ์š”๊ตฌ์„œ์—๋Š” `/n/n`์„ ๊ธฐ์ค€์œผ๋กœ `title`๊ณผ `body`๋ฅผ ๋‚˜๋ˆ„์–ด์ฃผ๋„๋ก ๋‚˜์™€์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. - `\n\n`์ด ์—†๋Š” ๊ฒฝ์šฐ์—๋Š” ์ œ๋Œ€๋กœ ์ €์žฅ์ด ๋˜์ง€ ์•Š๋Š” ์ƒํ™ฉ์ด ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค. #### PR ์งˆ๋ฌธ์‚ฌํ•ญ > ์š”๊ตฌ์„œ์˜ ์˜ˆ์‹œ ํ™”๋ฉด์„ ์ฐธ๊ณ ํ•˜์—ฌ, `textView`์—์„œ `\n\n`์„ ๊ธฐ์ค€์œผ๋กœ `title`๊ณผ `body`๋ฅผ ๊ตฌ๋ถ„ํ•ด์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค. `textView`์— `\n\n`์ด ์—†๋Š” ๊ฒฝ์šฐ CoreData์— ์ œ๋Œ€๋กœ ์ €์žฅ๋˜์ง€ ์•Š๋Š” ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค. > >์ด ์˜ค๋ฅ˜๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด `\n\n`์œผ๋กœ `components` ํ•ด ์ฃผ์—ˆ์„ ๊ฒฝ์šฐ, ๋ฐฐ์—ด์˜ ๊ฐœ์ˆ˜๊ฐ€ 2๊ฐœ ์ด์ƒ์ธ์ง€ ํ™•์ธํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ, 2๊ฐœ ๋ฏธ๋งŒ์ด๋ฉด ์ž„์˜๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ๋„ฃ์–ด์ฃผ๋„๋ก ํ–ˆ์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ `title`์— `\n`์ด ๋“ค์–ด๊ฐ„ ๊ฒฝ์šฐ, ์…€์—์„œ ์ œ๋ชฉ์ด ์ž˜๋ฆฌ๋Š” ํ˜„์ƒ์ด ๋ฐœ์ƒํ•ด `\n` ๋Œ€์‹  `" "` ๊ณต๋ฐฑ์œผ๋กœ ๋ฐ”๊ฟ”์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž ์ž…๋ ฅ์„ ์ž„์˜๋กœ ๋ฐ”๊ฟ”์คŒ์œผ๋กœ์จ ์‚ฌ์šฉ์ž๊ฐ€ ์›ํ•˜๋Š” ๊ฒฐ๊ณผ๋ฅผ ์ถฉ์กฑ์‹œ์ผœ์ฃผ์ง€ ๋ชปํ•  ์ˆ˜๋„ ์žˆ์„ ๊ฒƒ ๊ฐ™์•„ ์ข‹์€ ์ฝ”๋“œ๋ผ๋Š” ์ƒ๊ฐ์ด ๋“ค์ง€ ์•Š์Šต๋‹ˆ๋‹ค..๐Ÿ˜“ ๋” ๋‚˜์€ ๋ฐฉ์‹์œผ๋กœ `title`๊ณผ `body`๋ฅผ ๊ตฌ๋ถ„ํ•˜๊ณ , ์˜ˆ์™ธ์ฒ˜๋ฆฌ๋ฅผ ํ•ด ์ฃผ๊ณ  ์‹ถ์€๋ฐ ์–ด๋–ค ์•„์ด๋””์–ด๋กœ ์ ‘๊ทผํ•ด๋ณด๋ฉด ์ข‹์„์ง€ ๊ถ๊ธˆํ•ฉ๋‹ˆ๋‹ค! #### ๋ฆฌํŒฉํ† ๋งํ•œ ๊ฒฐ๊ณผ `/n/n`์ด ์—†๋”๋ผ๋„ ๊ตฌ๋ถ„์„ ํ•ด์„œ ์ €์žฅํ•ด์ฃผ๋„๋ก ์‚ฌ์šฉ์ž UX๋ฅผ ๊ณ ๋ คํ•˜์—ฌ ์ถ”๊ฐ€์ ์œผ๋กœ ๋ฆฌํŒฉํ† ๋ง์„ ํ•ด๋ณด์•˜์Šต๋‹ˆ๋‹ค. - ์˜ˆ์‹œ - `\n\n\n\n\n ์˜ˆํ†ค๊ณผ ํ˜„์ด์˜ ์ผ๊ธฐ์žฅ \n` - ๊ณ ๋ฏผ ์‚ฌํ•ญ - \n์ด ๋ฆฌ์ŠคํŠธ์—์„œ ํ•ด๋‹น ์ผ๊ธฐ์žฅ์˜ ์ œ๋ชฉ์œผ๋กœ ๋ณด์—ฌ์ ธ์•ผํ•˜๋Š”์ง€ - ์˜ˆํ†ค๊ณผ ํ˜„์ด์˜ ์ผ๊ธฐ์žฅ์ด ์ œ๋ชฉ์œผ๋กœ ๋ณด์—ฌ์ ธ์•ผํ•˜๋Š”์ง€ - ์ผ๊ธฐ์žฅ ์ƒ์„ธ์— ๋“ค์–ด์™”์„ ๋•Œ `\n\n\n\n\n` ๋“ค์ด ๋ณด์—ฌ์ง€๊ณ  ์˜ˆํ†ค๊ณผ ํ˜„์ด์˜ ์ผ๊ธฐ์žฅ์ด ๋‚˜์™€์•ผํ•˜๋Š”์ง€ - ํ•ด๊ฒฐ์•ˆ - > `์ผ๊ธฐ์˜ ๋งจ ์ฒซ ์ค„์€ ์ผ๊ธฐ์˜ ์ œ๋ชฉ์ด ๋˜๊ณ , ๊ทธ ๋‹ค์Œ ์ค„๋ถ€ํ„ฐ ๋ณธ๋ฌธ์ด ๋ฉ๋‹ˆ๋‹ค.` - ์œ„ ๋ช…์„ธ์™€ ๋ฌธ์ž๋งŒ ์…€์— ๋ณด์—ฌ์ง€๋Š” ๊ฒƒ์ด ์‚ฌ์šฉ์ž๊ฐ€ ๊ฐ€์žฅ ๊ธฐ๋Œ€ํ•˜๋Š” UX๊ฐ€ ์•„๋‹๊นŒ ์‹ถ์—ˆ์Šต๋‹ˆ๋‹ค. - ์ œ์ดํฌ์˜ ์กฐ์–ธ์— ๋”ฐ๋ผ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋ฆฌํŒฉํ† ๋ง์„ ์ง„ํ–‰ํ•˜์—ฌ ๊ณ ๋ฏผ์„ ํ•ด๊ฒฐํ–ˆ์Šต๋‹ˆ๋‹ค. - list์—์„œ ๋ณด์—ฌ์ ธ์•ผํ•˜๋Š” ์ œ๋ชฉ - `์˜ˆํ†ค๊ณผ ํ˜„์ด์˜ ์ผ๊ธฐ์žฅ` - ์ผ๊ธฐ ์ƒ์„ธ๋กœ ๋“ค์–ด์™”์„ ๋•Œ - `\n\n\n\n\n`์ด ๋‚˜ํƒ€๋‚œ ์ดํ›„ ์˜ˆํ†ค๊ณผ ํ˜„์ด์˜ ์ผ๊ธฐ์žฅ์ด ๋ณด์—ฌ์ง --- ### ๐Ÿ”— ์ฐธ๊ณ  ๋ฌธ์„œ - [UICollectionViewDiffableDataSource](https://developer.apple.com/documentation/uikit/uicollectionviewdiffabledatasource) - [NSDiffableDataSourceSnapshot](https://developer.apple.com/documentation/uikit/nsdiffabledatasourcesnapshot) - [Core Data](https://developer.apple.com/documentation/coredata) - [Making Apps with Core Data](https://developer.apple.com/videos/play/wwdc2019/230/) - [UIActivityViewController](https://developer.apple.com/documentation/uikit/uiactivityviewcontroller) - [LocalizedError](https://developer.apple.com/documentation/foundation/localizederror) - [resignFirstResponder](https://developer.apple.com/documentation/uikit/uiresponder/1621097-resignfirstresponder)