![](https://hackmd.io/_uploads/ByCvdPL_3.png) ## Ground Rules ### ๊ทœ์น™ - TIL, ์ผ์ผ ํšŒ๊ณ  ์ž‘์„ฑ ์‹œ๊ฐ„(๋งค์ผ 00์‹œ๋ถ€ํ„ฐ 1์‹œ๊ฐ„ ์ž‘์„ฑ ์ง„ํ–‰) - ํŽ˜์–ดํ”„๋กœ๊ทธ๋ž˜๋ฐ ์‹œ๊ฐ„ ์ œํ•œ ์ตœ๋Œ€ 30๋ถ„ ### ์Šคํฌ๋Ÿผ - ์˜ค์ „ 11์‹œ ๋””์Šค์ฝ”๋“œ์—์„œ ์ง„ํ–‰ - ๊ธˆ์ผ ์ง„ํ–‰ ์‚ฌํ•ญ ๊ณต์œ ํ•˜๊ธฐ(์˜ค๋Š˜์˜ ํ• ์ผ) ### ํ”„๋กœ์ ํŠธ ๊ทœ์น™ - ๋„ค์ด๋ฐ ์ค€์ˆ˜ํ•˜๊ธฐ(๊ฐ€์ด๋“œ ๋ผ์ธ) - ์ปค๋ฐ‹ ๋ฉ”์‹œ์ง€ ๊ทœ์น™ ์ง„ํ–‰ - ์ฝ”๋“œ์— ๋Œ€ํ•œ ๊ธฐ๋ก ๊ทธ๋•Œ๊ทธ๋•Œ ํ•˜๊ธฐ ## ์ผ์ผ ์Šคํฌ๋Ÿผ ### ๐Ÿ™Œ 06/26 - ์˜ค๋Š˜์˜ ์ปจ๋””์…˜ - idinaloq: ๋น„๊ฐ€์™€์„œ ๋„ˆ๋ฌด ์Šตํ•ด์š” ๐Ÿ’ฆ - hoon: ๋ฐฉํ•™์ด ํ–‰๋ณตํ–ˆ์Šต๋‹ˆ๋‹ค.๐Ÿ˜† - ํŠน์ด์‚ฌํ•ญ - idinaloq: ๋ฐฉํ•™๋•Œ ๊ณต๋ถ€๋ฅผ ์•ˆํ–ˆ์–ด์š”๐Ÿ˜ญ - hoon: ๋ฐฉํ•™์ด ๋” ํ•„์š”ํ•ด์š”...๐Ÿคฃ - ์˜ค๋Š˜ ํ•  ์ผ - [x] ํ™œ๋™ํ•™์Šต ์˜ˆ์Šต - [x] JSON ํŒŒ์ผ ๋ถ„์„ - [x] STEP 1 ๋ชจ๋ธ ํƒ€์ž… ๊ตฌํ˜„ ### ๐Ÿ™Œ 06/27 - ์˜ค๋Š˜์˜ ์ปจ๋””์…˜ - idinaloq: ์†์ด ์•ˆ์ข‹์•„์š”๐Ÿฆ๐Ÿฆ๐Ÿฆ - hoon: ํ‘น ์žค์Šต๋‹ˆ๋‹ค. - ํŠน์ด์‚ฌํ•ญ - idinaloq: ์˜ค๋Š˜์€ 2~3์‹œ์ „์— ์ž๋Š”๊ฑธ๋กœ...! - hoon: ์˜คํ›„์— ์šด๋™์„ ๋‹ค๋…€์˜ค๊ฒ ์Šต๋‹ˆ๋‹ค. ๐Ÿƒ๐Ÿป - ์˜ค๋Š˜ ํ•  ์ผ - [x] STEP 1 PR ์ฝ”๋ฉ˜ํŠธ ๋‹ต๋ณ€ ๋‹ฌ๊ธฐ - [ ] ์˜คํ† ๋ ˆ์ด์•„์›ƒ ์ •๋ณตํ•˜๊ธฐ ๊ณต๋ถ€ - [ ] ์ฝ”๋“œ๋ฒ ์ด์Šค๋กœ UI ๊ทธ๋ฆฌ๊ธฐ ๊ณต๋ถ€ ### ๐Ÿ™Œ 06/28 - ์˜ค๋Š˜์˜ ์ปจ๋””์…˜ - idinaloq: ํ‘น์ž์„œ ์ปจ๋””์…˜์ด ์ข‹์•„์š”! - hoon: ์กฐ๊ธˆ ํ”ผ๊ณคํ•ฉ๋‹ˆ๋‹ค๐Ÿคฃ - ํŠน์ด์‚ฌํ•ญ - idinaloq: ๋ฐ€๋ฆฐ TIL ์“ฐ๊ธฐ - hoon: ์˜คํ† ๋ ˆ์ด์•„์›ƒ ๋” ๊ณต๋ถ€ํ•ด์•ผ ํ•ด์š”.... - ๊ณตํ†ต: ์˜คํ›„ 8์‹œ ๋ฆฌ๋ทฐ์–ด ๋งŒ๋‚จ ๐Ÿ‘€ - ์˜ค๋Š˜ ํ•  ์ผ - [x] STEP2 ์ฒซ ๋ฒˆ์งธ ํ™”๋ฉด ๊ตฌํ˜„ํ•˜๊ธฐ - [x] ๋ฆฌ๋ทฐ์–ด ๋ฉด๋‹ด ### ๐Ÿ™Œ 06/29 - ์˜ค๋Š˜์˜ ์ปจ๋””์…˜ - idinaloq: ์ปจ๋””์…˜๋งŒ ์ข‹์Šต๋‹ˆ๋‹ค.. - hoon: ๋น„๊ฐ€ ์˜ค๋‹ˆ ๊ฟ‰๊ฟ‰ํ•ด์š”โ›ˆ๏ธ - ํŠน์ด์‚ฌํ•ญ - idinaloq: ์ง€๊ฐํ–ˆ์Šต๋‹ˆ๋‹ค..์ฃฝ๊ฒ ์Šต๋‹ˆ๋‹ค ๐Ÿ˜ญ๐Ÿ™‡โ€โ™‚๏ธ - hoon: ๊ฐ€๋Šฅํ•˜๋‹ค๋ฉด ๋‚ฎ์ž ์„...๐Ÿคฃ - ์˜ค๋Š˜ ํ•  ์ผ - [x] STEP2 TableView ๊ตฌํ˜„ํ•˜๊ธฐ - [x] ์„ธ ๋ฒˆ์งธ ๋ทฐ ๊ตฌํ˜„ํ•˜๊ธฐ ### ๐Ÿ™Œ 06/30 - ์˜ค๋Š˜์˜ ์ปจ๋””์…˜ - idinaloq: ์ตœ๊ณ ์ž…๋‹ˆ๋‹ค๐Ÿ‘ - hoon: ๋‚ซ๋ฐฐ๋“œ~๐Ÿ™‚ - ํŠน์ด์‚ฌํ•ญ - idinaloq: ์ผ์ฐ PRํ•  ์ˆ˜ ์žˆ๋‹ค๋ฉด ์•ˆ๊ฒฝ์„ ์ƒˆ๋กœ ๋งž์ถ”๊ธฐ! - hoon: ํ† ์š”์Šคํ„ฐ๋”” ์˜ˆ์Šต ๋ฐ ํŒ€์›๋“ค๊ณผ ์ž ๊น ์Šคํฌ๋Ÿผ์„ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค - ์˜ค๋Š˜ ํ•  ์ผ - [x] ๋ฆฌํŒฉํ† ๋ง - [x] STEP2 PR - [x] README ์ž‘์„ฑ ### ๐Ÿ™Œ 07/03 - ์˜ค๋Š˜์˜ ์ปจ๋””์…˜ - idinaloq: 2์ผ๋™์•ˆ ์ž˜ ์‰ฌ์–ด์„œ ์ข‹์•„์š” ใ…‹ใ…‹ใ…‹ - hoon: ์ฃผ๋ง์ด ๋„ˆ๋ฌด ํ–‰๋ณตํ–ˆ์Šต๋‹ˆ๋‹ค๐Ÿ˜† - ํŠน์ด์‚ฌํ•ญ - idinaloq: ์ž ๊น ๋ณผ์ผ ๋ณด๋Ÿฌ ๋‹ค๋…€์˜ค๊ฒ ์Šต๋‹ˆ๋‹ค - hoon: ํ™œ๋™ ํ•™์Šต ์˜ˆ์Šต๐Ÿ‘€ - ์˜ค๋Š˜ ํ•  ์ผ - [ ] PR ์ฝ”๋ฉ˜ํŠธ ๋‹ต๋ณ€ ๋‚จ๊ธฐ๊ธฐ - [x] ํ™œ๋™ ํ•™์Šต ์˜ˆ์Šต ๋ฐ ๊ฐœ์ธ ๊ณต๋ถ€ ### ๐Ÿ™Œ 07/04 - ์˜ค๋Š˜์˜ ์ปจ๋””์…˜ - idinaloq: ์š”์ฆ˜ ๋„ˆ๋ฌด ์ž˜ ์‰ฌ๋Š”๊ฑฐ๊ฐ™์•„์š”...์ข‹๋„ค์š” ใ…‹ใ…‹๐Ÿ˜… - hoon: ์žฅ๋งˆ๋ผ ์Šตํ•ด์š”๐ŸŒง๏ธ - ํŠน์ด์‚ฌํ•ญ - idinaloq: PR์ˆ˜์ • ์™„๋ฃŒํ•˜๊ณ  ์˜คํ† ๋ ˆ์ด์•„์›ƒ ๊ณต๋ถ€ํ•˜๊ธฐ - hoon: ๋น„๊ฐ€ ์˜ค์ง€ ์•Š๋Š”๋‹ค๋ฉด ์ €๋…์— ์šด๋™์„ ๋‹ค๋…€์˜ค๊ฒ ์Šต๋‹ˆ๋‹ค๐Ÿคฃ - ์˜ค๋Š˜ ํ•  ์ผ - [x] PR ์ฝ”๋ฉ˜ํŠธ ๋‹ต๋ณ€ ๋‚จ๊ธฐ๊ธฐ ### ๐Ÿ™Œ 07/05 - ์˜ค๋Š˜์˜ ์ปจ๋””์…˜ - idinaloq: ๊ทธ์ €๊ทธ๋ž˜์š”๐Ÿ˜… - hoon: ํ”ผ๊ณคํ•ฉ๋‹ˆ๋‹ค๐Ÿ˜ญ - ํŠน์ด์‚ฌํ•ญ - idinaloq: ์˜ค๋Š˜ํ• ์ผ ๋๋‚ด๊ธฐ - hoon: ๊ฐ€๋Šฅํ•˜๋‹ค๋ฉด ๋‚ฎ์ž ๊ณผ ์šด๋™์„... - ์˜ค๋Š˜ ํ•  ์ผ - [x] STEP 3 ๊ตฌํ˜„ - [ ] STEP 3 PR ๋ณด๋‚ด๊ธฐ ### ๐Ÿ™Œ 07/06 - ์˜ค๋Š˜์˜ ์ปจ๋””์…˜ - idinaloq: ์กธ๋ฆฌ๋„ค์š”.. - hoon: ์•„์นจ์ด ํž˜๋“ค์–ด์š”๐Ÿคฃ - ํŠน์ด์‚ฌํ•ญ - idinaloq: ๋นจ๋ฆฌ๋๋‚ด๊ณ  ๋‚ฎ์ž ์ž๊ธฐ!!(์˜คํ›„5์‹œ์ดํ›„ ์˜ˆ์ƒ) - hoon: ๋‚ฎ์ž ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค๐Ÿฅบ - ์˜ค๋Š˜ ํ•  ์ผ - [x] STEP 3 PR ๋ณด๋‚ด๊ธฐ - [x] ํ™œ๋™ ํ•™์Šต ์˜ˆ์Šต ### ๐Ÿ™Œ 07/07 - ์˜ค๋Š˜์˜ ์ปจ๋””์…˜ - idinaloq: ๋นจ๋ฆฌ...๋นจ๋ฆฌ๋‚ด์ผ...๐Ÿ˜ต - hoon: ๋๋‚ด๊ณ  ์ž˜ ์ƒ๊ฐ์— ํ–‰๋ณตํ•ฉ๋‹ˆ๋‹ค๐Ÿฅณ - ํŠน์ด์‚ฌํ•ญ - idinaloq: ํ”„๋กœ์ ํŠธ ๋งˆ๋ฌด๋ฆฌํ•˜๊ธฐ! - hoon: ์˜ค๋Š˜์ด ๋งˆ์ง€๋ง‰ ๋‚ ๐Ÿ˜† - ์˜ค๋Š˜ ํ•  ์ผ - [x] STEP 3 PR ๋‹ต๋ณ€ - [x] README ์ž‘์„ฑ # ๐ŸŽช ๋งŒ๊ตญ๋ฐ•๋žŒํšŒ ## ๐Ÿ“– ๋ชฉ์ฐจ 1. [์†Œ๊ฐœ](#-์†Œ๊ฐœ) 2. [ํŒ€์›](#-ํŒ€์›) 3. [ํƒ€์ž„๋ผ์ธ](#-ํƒ€์ž„๋ผ์ธ) 4. [์‹œ๊ฐํ™”๋œ ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ](#-์‹œ๊ฐํ™”๋œ-ํ”„๋กœ์ ํŠธ-๊ตฌ์กฐ) 5. [์‹คํ–‰ ํ™”๋ฉด](#-์‹คํ–‰-ํ™”๋ฉด) 6. [ํŠธ๋Ÿฌ๋ธ” ์ŠˆํŒ…](#-ํŠธ๋Ÿฌ๋ธ”-์ŠˆํŒ…) 7. [์ฐธ๊ณ  ๋งํฌ](#-์ฐธ๊ณ -๋งํฌ) 8. [ํŒ€ ํšŒ๊ณ ](#-ํŒ€-ํšŒ๊ณ ) </br> ## ๐Ÿ€ ์†Œ๊ฐœ ํ›ˆ, ์ด๋””๋‚˜๋กœํฌ(`hoon`, `idinaloq`) ํŒ€์ด ๋งŒ๋“  ๋งŒ๊ตญ๋ฐ•๋žŒํšŒ ๊ฐ€์ด๋“œ ์•ฑ์ž…๋‹ˆ๋‹ค. 1900.04.14 ~ 1900.11.12 ๊นŒ์ง€ ๊ฐœ์ตœ๋œ ํŒŒ๋ฆฌ ๋งŒ๊ตญ๋ฐ•๋žŒํšŒ ์ค‘ ํ•œ๊ตญ ์ถœํ’ˆ์ž‘์„ ์†Œ๊ฐœํ•ฉ๋‹ˆ๋‹ค. ๊ฐ๊ฐ์˜ ํ…Œ์ด๋ธ” ๋ทฐ ์…€์„ ํด๋ฆญํ•˜๋ฉด ์ž์„ธํ•œ ์ถœํ’ˆ์ž‘ ๋‚ด์šฉ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. * ์ฃผ์š” ๊ฐœ๋…: `UITableView`, `UITableViewCell`, `JSON`, `JSONDecoder`, `Dynamic Type`, `AutoLayout`, `appDelegate`, `sceneDelegate` </br> ## ๐Ÿ‘จโ€๐Ÿ’ป ํŒ€์› | idinaloq | hoon | | :--------: | :--------: | | <Img src = "https://user-images.githubusercontent.com/109963294/235301015-b81055d2-8618-433c-b680-58b6a38047d9.png" width = "200" height="200"/> |<Img src="https://i.imgur.com/zXoi5OC.jpg" width="200" height="200"> | |[Github Profile](https://github.com/idinaloq) |[Github Profile](https://github.com/Hoon94) | </br> ## โฐ ํƒ€์ž„๋ผ์ธ |๋‚ ์งœ|๋‚ด์šฉ| |:--:|--| |2023.06.26.| - `ExpositionUniverselle`, `Item` ํƒ€์ž… ์ƒ์„ฑ | |2023.06.27.| - `ExpositionUniverselle`, `Item` ํƒ€์ž… ๋ฆฌํŒฉํ† ๋ง | |2023.06.28.| - `ExpositionUniverselleViewController UI` ๊ตฌํ˜„ <br> - `JSON` ํŒŒ์ผ ๋””์ฝ”๋”ฉ <br> - `touchUpExpositionItemListButton` ๋ฉ”์„œ๋“œ ์ถ”๊ฐ€ | |2023.06.29.| - `ExpositionItemListViewController UI` ๊ตฌํ˜„ <br> - `itemListTableView` ์ถ”๊ฐ€ <br> - `ItemUITableViewCellStyleSubtitle` ํด๋ž˜์Šค ์ƒ์„ฑ <br> - `ExpositionItemViewController UI` ๊ตฌํ˜„ <br> - ๋ฆฌํŒฉํ† ๋ง | |2023.06.30.| - ๋ฆฌํŒฉํ† ๋ง <br> - README ์ž‘์„ฑ | |2023.07.01.| - `lazy var`๋กœ ์„ ์–ธ๋œ ํ”„๋กœํผํ‹ฐ ์ˆ˜์ • <br> - `ExpositionUniverselle` ํƒ€์ž… ์—ฐ์‚ฐ ํ”„๋กœํผํ‹ฐ ์ถ”๊ฐ€ <br> - `viewDidLoad` ์ˆ˜์ • <br> - ๋งค์ง๋ฆฌํ„ฐ๋Ÿด ์ˆ˜์ •, `JSONFile` ํƒ€์ž… ์ถ”๊ฐ€ | |2023.07.03.| - `configureCell` ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ `cell`์— ์ ‘๊ทผํ•˜๋„๋ก ์ˆ˜์ • <br> - `itemImage` ํด๋กœ์ € ์ œ๊ฑฐ, `expositionItem` ์ƒ์ˆ˜๋กœ ๋ณ€๊ฒฝ <br> - `addArrangedSubviews` ๋งค๊ฐœ๋ณ€์ˆ˜์ธ `subviews` ํƒ€์ž… ์ˆ˜์ • <br> - `JSONFile` ํƒ€์ž…์ด `CustomStringConvertible` ์ฑ„ํƒํ•˜๋„๋ก ์ˆ˜์ • | |2023.07.04.| - `viewDidLoad` ์ถ”์ƒํ™” ์ˆ˜์ค€ ์ˆ˜์ • <br> - `AlertController` ์ถ”๊ฐ€ | |2023.07.05.| - `applicaion(_:supportedInterfaceOrientationsFor:)` ๋ฉ”์„œ๋“œ ์ถ”๊ฐ€ <br> - `ExpositionUniverselleViewController` ์˜คํ† ๋ ˆ์ด์•„์›ƒ ์ œ์•ฝ ์ถ”๊ฐ€ <br> - `configureItemImageConstraint` ๋ฉ”์„œ๋“œ ์ถ”๊ฐ€ <br> - ๊ธฐ์กด์˜ ์…€์„ ์ปค์Šคํ…€์…€๋กœ ๋ณ€๊ฒฝ <br> - ์˜คํ† ๋ ˆ์ด์•„์›ƒ ์ œ์•ฝ์กฐ๊ฑด ์ˆ˜์ •| |2023.07.06.| - `String+Extension` ์ถ”๊ฐ€ <br> - `dynamic type` ์ ์šฉ๋˜๋„๋ก ๋ ˆ์ด๋ธ” ์ˆ˜์ • <br> - `addTarget`์„ `addAction`์œผ๋กœ ์ˆ˜์ • <br> | |2023.07.07.| - `cell imageView constraints` ์ˆ˜์ • <br> - `itemImage`์˜ `contentCompressionResistance priority` ๋ณ€๊ฒฝ <br> - `changeOrientation` ๋„ค์ด๋ฐ ์ˆ˜์ • ๋ฐ ๊ฐ€๋…์„ฑ ๊ฐœ์„  <br> - `configureCell` ๋ฉ”์„œ๋“œ ์ˆ˜์ • <br> - README ์ž‘์„ฑ | </br> ## ๐Ÿ‘€ ์‹œ๊ฐํ™”๋œ ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ ### โ„น๏ธ File Tree โ”Œโ”€โ”€ Expo1900 โ”‚ย ย  โ””โ”€โ”€ Expo1900 โ”‚ย ย  ย ย  โ”œโ”€โ”€ Model โ”‚ย ย  ย ย  โ”‚ย ย  โ”œโ”€โ”€ ExpositionUniverselle โ”‚ย ย  ย ย  โ”‚ย ย  โ”œโ”€โ”€ Item โ”‚ย ย  ย ย  โ”‚ย ย  โ””โ”€โ”€ JSONFile โ”‚ย ย  ย ย  โ”œโ”€โ”€ View โ”‚ย ย  ย ย  โ”‚ย ย  โ”œโ”€โ”€ LaunchScreen โ”‚ย ย  ย ย  โ”‚ย ย  โ””โ”€โ”€ ExpositionItemCell โ”‚ย ย  ย ย  โ”œโ”€โ”€ Controller โ”‚ย ย  ย ย  โ”‚ย ย  โ”œโ”€โ”€ AppDelegate โ”‚ย ย  ย ย  โ”‚ย ย  โ”œโ”€โ”€ SceneDelegate โ”‚ย ย  ย ย  โ”‚ย ย  โ”œโ”€โ”€ ExpositionUniverselleViewController โ”‚ย ย  ย ย  โ”‚ย ย  โ”œโ”€โ”€ ExpositionItemListViewController โ”‚ย ย  ย ย  โ”‚ย ย  โ”œโ”€โ”€ ExpositionItemViewController โ”‚ย ย  ย ย  โ”‚ย ย  โ””โ”€โ”€ AlertController โ”‚ย ย  ย ย  โ”œโ”€โ”€ Extension โ”‚ย ย  ย ย  โ”‚ โ”œโ”€โ”€ JSONDecoder+Extension โ”‚ย ย  ย ย  โ”‚ โ””โ”€โ”€ String+Extension โ”‚ย ย  ย ย  โ”œโ”€โ”€ Error โ”‚ย ย  ย ย  โ”‚ย ย  โ””โ”€โ”€ NSDataAssetError โ”‚ย ย  ย  ย  โ”œโ”€โ”€ Assets โ”‚ย ย  ย  โ””โ”€โ”€ Info โ”‚ โ”œโ”€โ”€ ํŒ€ํšŒ๊ณ .md โ””โ”€โ”€ README.md ### ๐Ÿ“ Class Diagram <p align="center"> <img width="1000" src="https://hackmd.io/_uploads/r1CkYErY3.jpg"> </p> </br> ## ๐Ÿ’ป ์‹คํ–‰ ํ™”๋ฉด | ์‹คํ–‰ ํ™”๋ฉด(์„ธ๋กœ) | ๋””์ฝ”๋”ฉ ์˜ค๋ฅ˜ | ํฐํŠธ ํฌ๊ธฐ ๋ณ€๊ฒฝ | |:-------------:|:--------:| :--------: | |<Img src="https://github.com/Hoon94/SwiftAlgorithm/assets/43189761/b9701fea-486c-405c-b15e-8531946e3777" width="250">|<Img src="https://github.com/Hoon94/SwiftAlgorithm/assets/43189761/24187f3c-0085-42f6-be1c-9a6c750605c7" width="250">|<Img src="https://github.com/Hoon94/SwiftAlgorithm/assets/43189761/3df7f44a-1bfb-41b1-b616-fad2d825ca7f" width="250">| | ์‹คํ–‰ ํ™”๋ฉด(๊ฐ€๋กœ) | |:-------------:| |<Img src="https://github.com/Hoon94/SwiftAlgorithm/assets/43189761/a7630713-0a66-4555-8bbc-cd8059b4c388" height="250">| </br> ## ๐Ÿงจ ํŠธ๋Ÿฌ๋ธ” ์ŠˆํŒ… 1๏ธโƒฃ **JSON ํฌ๋ฉง์˜ ๋ฐ์ดํ„ฐ์™€ ๋งค์นญํ•  ๋ชจ๋ธ ํƒ€์ž…** <br> - ๐Ÿ”’ **๋ฌธ์ œ์ ** <br> `exposition_universelle_1900.json`๊ณผ `items.json`์—์„œ ์‚ฌ์šฉํ•˜๋Š” `JSON` ํฌ๋งท์˜ ๋ฐ์ดํ„ฐ๋Š” ๋„ค์ด๋ฐ์„ snake case ํ˜•ํƒœ๋กœ ์‚ฌ์šฉํ•˜์˜€์Šต๋‹ˆ๋‹ค. Swift์—์„œ ์‚ฌ์šฉํ•˜๋Š” camel case ํ˜•ํƒœ์™€ ๋‹ฌ๋ž๊ณ , ์ถ•์•ฝ์–ด๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋„ค์ด๋ฐ๋„ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. `JSONDecoder`๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋””์ฝ”๋”ฉ์„ ํ•˜๋ฉด JSON ํŒŒ์ผ์— ์ €์žฅ๋œ ๋„ค์ด๋ฐ์„ ์ €ํฌ๊ฐ€ ์ƒ์„ฑํ•œ ํƒ€์ž…์˜ ํ”„๋กœํผํ‹ฐ์˜ ๋„ค์ด๋ฐ๊ณผ ์ผ์น˜์‹œ์ผœ์•ผ ํ–ˆ์ง€๋งŒ, Swift์—์„œ ์‚ฌ์šฉํ•˜๋Š” [๋„ค์ด๋ฐ ๊ฐ€์ด๋“œ๋ผ์ธ](https://www.swift.org/documentation/api-design-guidelines/#naming)์—์„œ ํƒ€์ž…๊ณผ ํ”„๋กœํ† ์ฝœ์€ **UpperCamelCase**๋กœ, ๋‚˜๋จธ์ง€๋Š” **lowerCamelCase**๋กœ ์ž‘์„ฑํ•ด์•ผ ๋œ๋‹ค๋Š” ๊ฐ€์ด๋“œ๋ผ์ธ๊ณผ ๋‹ค๋ฅด๋‹ค๋Š” ๋ฌธ์ œ๊ฐ€ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ๐Ÿ”‘ **ํ•ด๊ฒฐ๋ฐฉ๋ฒ•** <br> `CodingKeys`๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ `JSON` ํŒŒ์ผ์— ์ €์žฅ๋œ ๋„ค์ด๋ฐ์„ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๊ฐ๊ฐ์˜ ํ”„๋กœํผํ‹ฐ์˜ ์ผ€์ด์Šค์— ๋งž๊ฒŒ ์ง€์ •ํ•˜์˜€์Šต๋‹ˆ๋‹ค. ```swift struct Item: Decodable { let name: String let imageName: String let shortDescription: String let totalDescription: String enum CodingKeys: String, CodingKey { case name case imageName = "image_name" case shortDescription = "short_desc" case totalDescription = "desc" } } ``` <br> 2๏ธโƒฃ **์ฝ”๋“œ๋ฒ ์ด์Šค๋กœ `UI` ๊ตฌํ˜„ํ•˜๊ธฐ** <br> - ๐Ÿ”’ **๋ฌธ์ œ์ ** <br> ์ง€๊ธˆ๊นŒ์ง€์˜ ํ”„๋กœ์ ํŠธ๋Š” ์Šคํ† ๋ฆฌ๋ณด๋“œ๋ฅผ ์ด์šฉํ•ด์„œ `UI`๋ฅผ ๊ตฌํ˜„ํ–ˆ์Šต๋‹ˆ๋‹ค. ์ง€๊ธˆ์˜ ์†Œ๊ทœ๋ชจ ํ”„๋กœ์ ํŠธ๋Š” ๊ดœ์ฐฎ์ง€๋งŒ, ๋งŒ์•ฝ ํ”„๋กœ์ ํŠธ๊ฐ€ ๋ณต์žกํ•ด์ง„๋‹ค๋ฉด ํ˜‘์—…์„ ํ•  ๋•Œ ์Šคํ† ๋ฆฌ๋ณด๋“œ์—์„œ์˜ ์ถฉ๋Œ์ด ์žˆ์„ ๊ฐ€๋Šฅ์„ฑ์ด ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์ฝ”๋“œ๋ฒ ์ด์Šค๋กœ `UI`๋ฅผ ๊ตฌํ˜„ํ•˜๊ธฐ๋กœ ํ–ˆ์ง€๋งŒ, `View`๋ฅผ ์ถ”๊ฐ€ํ•ด๋„ ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ์— ์•„๋ฌด๊ฒƒ๋„ ํ‘œ์‹œ๋˜์ง€ ์•Š๋Š” ๋ฌธ์ œ๊ฐ€ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ๐Ÿ”‘ **ํ•ด๊ฒฐ๋ฐฉ๋ฒ•** <br> ํ•ด๋‹น ๋ฌธ์ œ๋Š” ๊ฐ๊ฐ์˜ ๋ทฐ์— ์ œ์•ฝ์กฐ๊ฑด์„ ์„ค์ •ํ•˜์ง€ ์•Š์•˜๋˜ ๋ฌธ์ œ๋กœ, ์Šคํ† ๋ฆฌ๋ณด๋“œ ์ƒ์—์„œ `UI`๋ฅผ ์ถ”๊ฐ€ํ•  ๋•Œ ๊ธฐ๋ณธ์ ์œผ๋กœ ์ƒ์ˆ˜ ๊ฐ’์ธ `constant`๋กœ ์ง€์ •๋˜์–ด์„œ `UI`๊ฐ€ ๋ณด์ด์ง€๋งŒ, ์ฝ”๋“œ๋ฒ ์ด์Šค๋กœ `UI`๋ฅผ ๊ตฌํ˜„ํ•  ๋•Œ๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ์•„๋ฌด ๊ฐ’๋„ ์—†๊ธฐ ๋•Œ๋ฌธ์— `UI`๊ฐ€ ํ‘œ์‹œ๋˜์ง€ ์•Š์•˜๋˜ ๋ฌธ์ œ์˜€์Šต๋‹ˆ๋‹ค. `topAnchor`, `leadingAnchor`, `trailingAnchor`, `bottomAnchor`, `heightAnchor`, `widthAnchor`๋“ฑ์˜ ์ œ์•ฝ์กฐ๊ฑด์„ ์‚ฌ์šฉํ•ด์„œ ํ™”๋ฉด์˜ ์–ด๋А ์œ„์น˜์— ๋ทฐ๋ฅผ ๋„์šธ์ง€์— ๋Œ€ํ•œ ์˜คํ† ๋ ˆ์ด์•„์›ƒ ์ œ์•ฝ์กฐ๊ฑด์„ ์„ค์ •ํ–ˆ์Šต๋‹ˆ๋‹ค. <br> 3๏ธโƒฃ **๋ทฐ ์—…๋ฐ์ดํŠธ** <br> - ๐Ÿ”’ **๋ฌธ์ œ์ ** <br> ๋ทฐ ์ปจํŠธ๋กค๋Ÿฌ์—์„œ ์—ฐ์‚ฐ ํ”„๋กœํผํ‹ฐ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ทฐ๋ฅผ ์ƒ์„ฑํ•  ๋•Œ `UILabel`์˜ ๊ฒฝ์šฐ ์ž…๋ ฅ๋˜์–ด์•ผ ํ•˜๋Š” ๋‚ด์šฉ๋“ค์„ ํ•จ๊ป˜ ์ž…๋ ฅํ•˜์—ฌ ์ƒ์„ฑํ•˜์˜€์Šต๋‹ˆ๋‹ค. ```swift private var expositionUniverselle: ExpositionUniverselle? private lazy var titleLabel: UILabel = { let label: UILabel = UILabel() label.text = = expositionUniverselle?.title label.textAlignment = .center return label }() ``` ์œ„์™€ ๊ฐ™์ด ์‚ฌ์šฉํ•˜๋ฉด `expositionUniverselle`์ด ์ดˆ๊ธฐํ™” ์ „์— ๊ฐ’์— ์ ‘๊ทผํ•˜์—ฌ `title` ๊ฐ’์„ ๋ฐ›์•„์˜ค๋ ค๊ณ  ํ•˜๊ธฐ ๋•Œ๋ฌธ์— `lazy var`๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ–ˆ์Šต๋‹ˆ๋‹ค. ๐Ÿ”‘ **ํ•ด๊ฒฐ๋ฐฉ๋ฒ•** <br> ๋ทฐ๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ์ดํ›„์— `updateLabel()` ๋ฉ”์„œ๋“œ์—์„œ `label.text`์— ๊ฐ’์„ ํ• ๋‹นํ•˜๋Š” ์ž‘์—…๋“ค์„ ๋ชจ์•„์„œ ์ง„ํ–‰ํ•˜๋„๋ก ํ•˜์˜€์Šต๋‹ˆ๋‹ค. ```swift private var expositionUniverselle: ExpositionUniverselle? private let titleLabel: UILabel = { let label: UILabel = UILabel() label.textAlignment = .center return label }() ... private func updateLabel() { navigationItem.title = "๋ฉ”์ธ" titleLabel.text = expositionUniverselle?.titleForLabel visitorsLabel.text = expositionUniverselle?.visitorsForLabel locationLabel.text = expositionUniverselle?.locationForLabel durationLabel.text = expositionUniverselle?.durationForLabel totalDescriptionLabel.text = expositionUniverselle?.totalDescription } ``` <br> 4๏ธโƒฃ **๋งค์ง๋ฆฌํ„ฐ๋„ ์ตœ์†Œํ™”** <br> - ๐Ÿ”’ **๋ฌธ์ œ์ ** <br> `exposition_universelle_1900`, `items`๊ฐ™์€ `JSON` ํŒŒ์ผ์—์„œ ์ง์ ‘์ ์œผ๋กœ ํŒŒ์ผ์„ ์ฝ์–ด์˜ฌ ๋•Œ ์•„๋ž˜์™€ ๊ฐ™์ด ํŒŒ์ผ ์ด๋ฆ„์„ ์ง์ ‘ ์ง€์ •ํ•˜์—ฌ ์ฝ์–ด์˜ค๋„๋ก ํ•˜์˜€์Šต๋‹ˆ๋‹ค. ```swift private func decodeExpositionUniverselle() { do { expositionUniverselle = try JSONDecoder().decode(ExpositionUniverselle.self, from: "exposition_universelle_1900") } catch { print(error) } } ``` ์ถ”ํ›„ ํŒŒ์ผ์˜ ์ด๋ฆ„์ด๋‚˜ ํŒŒ์ผ ์ž์ฒด๊ฐ€ ๋ฐ”๋€Œ๋Š” ๊ฒฝ์šฐ ํŒŒ์ผ ์ด๋ฆ„์„ ๊ณ„์† ๋ณ€๊ฒฝํ•ด์•ผ ๋˜๋Š”๋ฐ, ๋งŒ์•ฝ ๋ทฐ ์ปจํŠธ๋กค๋Ÿฌ๊ฐ€ ์ปค์ง€๊ฑฐ๋‚˜ ํŒŒ์ผ์ด ๋งŽ์•„์ง€๋Š” ๊ฒฝ์šฐ ์ผ์ผ์ด ํŒŒ์ผ๋ช…์„ ์ž…๋ ฅํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ์ฐพ์•„ ๋ณ€๊ฒฝํ•˜๊ธฐ ์–ด๋ ค์šธ ๊ฒƒ ๊ฐ™๋‹ค๊ณ  ์ƒ๊ฐํ–ˆ์Šต๋‹ˆ๋‹ค. ๐Ÿ”‘ **ํ•ด๊ฒฐ๋ฐฉ๋ฒ•** <br> ์ด๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด `JSONFile.swift` ํŒŒ์ผ์„ ๋งŒ๋“ค๊ณ  ํŒŒ์ผ ์ด๋ฆ„์„ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋„๋ก ํ–ˆ์Šต๋‹ˆ๋‹ค. ```swift enum JSONFile: CustomStringConvertible { case exposition case items var description: String { switch self { case .exposition: return "exposition_universelle_1900" case .items: return "items" } } } ``` ์ด๋ ‡๊ฒŒ ํ•˜๋‚˜์˜ ํŒŒ์ผ์—์„œ `JSON` ํŒŒ์ผ์„ ๊ด€๋ฆฌํ•˜๊ธฐ ๋•Œ๋ฌธ์— ํŒŒ์ผ์˜ ์ˆ˜์ •์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ ํŒŒ์ผ๋ช…์„ ์ž…๋ ฅํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ์ฐพ์•„๊ฐˆ ํ•„์š” ์—†์ด `JSONFile.swift` ํŒŒ์ผ์—์„œ ๋ชจ๋‘ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ์œ„์˜ `JSONFile` ํƒ€์ž…์„ ์ ์šฉํ•˜์—ฌ ์•„๋ž˜์™€ ๊ฐ™์ด ์ฝ”๋“œ๋ฅผ ์ˆ˜์ •ํ•˜์˜€์Šต๋‹ˆ๋‹ค. ```swift private func decodeExpositionUniverselle() { do { expositionUniverselle = try JSONDecoder().decode(ExpositionUniverselle.self, from: JSONFile.exposition.name) } catch { print(error) } } ``` <br> 5๏ธโƒฃ **๋‹ค์Œ ๋ทฐ ์ปจํŠธ๋กค๋Ÿฌ์— ๋ฐ์ดํ„ฐ ์ „๋‹ฌ** <br> - ๐Ÿ”’ **๋ฌธ์ œ์ ** <br> ๋‘ ๋ฒˆ์งธ ๋ทฐ ์ปจํŠธ๋กค์ธ `ExpositionItemViewController`์—์„œ `JSON` ๋ฐ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ `decodeItems()` ๋ฉ”์„œ๋“œ๋ฅผ ํ†ตํ•ด ๋””์ฝ”๋”ฉ ๋œ ๋ฐ์ดํ„ฐ๋ฅผ `itemList`์— ํ• ๋‹นํ•ฉ๋‹ˆ๋‹ค. ์„ธ ๋ฒˆ์งธ ๋ทฐ ์ปจํŠธ๋กค์ธ `ExpositionItemListViewController`์—์„œ๋„ ์œ„์™€ ๊ฐ™์ด ๋””์ฝ”๋”ฉ์„ ํ•˜๋ฉด ๊ฐ™์€ ๊ฒฐ๊ณผ๊ฐ€ ๋‚˜์˜ค์ง€๋งŒ, ๋™์ผํ•œ ํŒŒ์ผ์„ ๋‘ ๋ฒˆ ๋””์ฝ”๋”ฉ ํ•˜๋Š” ์ค‘๋ณต๋œ ๋™์ž‘์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค. ๐Ÿ”‘ **ํ•ด๊ฒฐ๋ฐฉ๋ฒ•** <br> ๋‘ ๋ฒˆ์งธ ๋ทฐ ์ปจํŠธ๋กค์—์„œ ๋‹ค์Œ ๋ทฐ ์ปจํŠธ๋กค๋Ÿฌ์— ๋ฐ์ดํ„ฐ๋ฅผ ๋„˜๊ฒจ์ฃผ์–ด ๋™์ผํ•œ ์ž‘์—…์„ ์—ฌ๋Ÿฌ ๋ฒˆ ๋ฐ˜๋ณตํ•˜์ง€ ์•Š๋„๋ก ํ•˜์˜€์Šต๋‹ˆ๋‹ค. ํŠนํžˆ ์„ธ ๋ฒˆ์งธ ํ™”๋ฉด์€ ๊ฐ ์•„์ดํ…œ์— ๋Œ€ํ•œ ์ƒ์„ธ ์ •๋ณด๋ฅผ ๋ณด์—ฌ์ฃผ๋Š” ํ™”๋ฉด์ž…๋‹ˆ๋‹ค. ์„ธ ๋ฒˆ์งธ ํ™”๋ฉด์—์„œ ์ „์ฒด ์•„์ดํ…œ ๋ชฉ๋ก์„ ๋””์ฝ”๋”ฉ ํ•˜์—ฌ ์‚ฌ์šฉํ•˜๊ธฐ๋ณด๋‹ค๋Š” ๊ฐœ๋ณ„ ์•„์ดํ…œ์— ๋Œ€ํ•œ ๋ฐ์ดํ„ฐ๋งŒ์„ ์ด์ „ ํ™”๋ฉด์—์„œ ์ „๋‹ฌ๋ฐ›์•„ ์‚ฌ์šฉํ•˜๋„๋ก ์•„๋ž˜์™€ ๊ฐ™์ด ๊ตฌํ˜„ํ–ˆ์Šต๋‹ˆ๋‹ค. ```swift func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) let expositionItemViewController: ExpositionItemViewController = ExpositionItemViewController(item: itemList[indexPath.row]) navigationController?.pushViewController(expositionItemViewController, animated: true) } ``` <br> 6๏ธโƒฃ **`JSONDecoder Extension`** <br> - ๐Ÿ”’ **๋ฌธ์ œ์ ** <br> ๊ฐ™์€ `decode()` ๋ฉ”์„œ๋“œ๋ฅผ ํ˜„์žฌ ๋‘ ๋ฒˆ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. `do - try - catch` ๊ณผ์ •์—์„œ `NSDataAsset`์„ ์˜ต์…”๋„ ๋ฐ”์ธ๋”ฉ์„ ํ•ด์„œ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. `decode()` ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ํšŸ์ˆ˜๊ฐ€ ๋Š˜์–ด๋‚œ๋‹ค๋ฉด, `NSDataAsset`์— ๋Œ€ํ•œ ์˜ต์…”๋„ ๋ฐ”์ธ๋”ฉ ์ฝ”๋“œ๊ฐ€ ์ค‘๋ณต๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๐Ÿ”‘ **ํ•ด๊ฒฐ๋ฐฉ๋ฒ•** <br> `Extension`์œผ๋กœ `decode`์˜ ๊ธฐ๋Šฅ์„ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ํ™•์žฅํ•˜์—ฌ ์‚ฌ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค. ```swift extension JSONDecoder { func decode<T: Decodable>(_ type: T.Type, from assetName: String) throws -> T { guard let json = NSDataAsset(name: assetName) else { throw NSDataAssetError.invalidDataAsset } return try JSONDecoder().decode(type, from: json.data) } } ``` <br> 7๏ธโƒฃ **์‚ฌ์šฉ์ž์—๊ฒŒ ์—๋Ÿฌ ์•Œ๋ฆฌ๊ธฐ** <br> - ๐Ÿ”’ **๋ฌธ์ œ์ ** <br> ๊ธฐ์กด ์ฝ”๋“œ์—์„œ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜๋Š” ๊ฒฝ์šฐ `print(error)`์™€ ๊ฐ™์ด ํ„ฐ๋ฏธ๋„์— ์•Œ๋ฆฌ๋„๋ก ๊ตฌํ˜„์„ ํ–ˆ์Šต๋‹ˆ๋‹ค. ๋งŒ์•ฝ ์•ฑ์—์„œ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ ์‚ฌ์šฉ์ž๋“ค์€ ์—๋Ÿฌ ๋ฐœ์ƒ ์ƒํ™ฉ์„ ์ธ์ง€ํ•˜์ง€ ๋ชปํ•˜๋Š” ๋ฌธ์ œ๊ฐ€ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ```swift do { itemList = try JSONDecoder().decode([Item].self, from: JSONFile.items.description) } catch NSDataAssetError.invalidDataAsset { ... } catch { print(error) } ``` ๐Ÿ”‘ **ํ•ด๊ฒฐ๋ฐฉ๋ฒ•** <br> ์•ฑ์„ ์‚ฌ์šฉํ•˜๋Š” ์‚ฌ์šฉ์ž๋“ค์—๊ฒŒ ์—๋Ÿฌ ๋ฐœ์ƒ๊ณผ ๊ฐ™์€ ํฌ๋ฆฌํ‹ฐ์ปฌํ•œ ์ƒํ™ฉ์„ ์•Œ๋ฆฌ๊ณ  ์‹ถ์„ ๋•Œ, [UIAlertController](https://developer.apple.com/documentation/uikit/uialertcontroller)๋ฅผ ์‚ฌ์šฉํ•ด์„œ ์‚ฌ์šฉ์ž์—๊ฒŒ [Alerts](https://developer.apple.com/design/human-interface-guidelines/alerts)์„ ํ‘œ์‹œํ•˜๋Š” ๋ฐฉ๋ฒ•์ด ์—๋Ÿฌ ๋ฐœ์ƒ ์ƒํ™ฉ์„ ์•Œ๋ฆฌ๋Š” ๊ฐ€์žฅ ์ข‹์€ ๋ฐฉ๋ฒ•์ด๋ผ๊ณ  ์ƒ๊ฐํ–ˆ๊ณ , ์ผ๋ฐ˜์ ์œผ๋กœ๋„ ์ด๋Ÿฌํ•œ ๋ฐฉ๋ฒ•์„ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์–ผ๋Ÿฟ์œผ๋กœ ์—๋Ÿฌ ๋ฐœ์ƒ์„ ์•Œ๋ฆฌ๋„๋ก ์ถ”๊ฐ€ํ–ˆ์Šต๋‹ˆ๋‹ค. ```swift do { itemList = try JSONDecoder().decode([Item].self, from: JSONFile.items.description) } catch NSDataAssetError.invalidDataAsset { let alert: UIAlertController = AlertController().configureAlert(errorName: NSDataAssetError.invalidDataAsset.localizedDescription) present(alert, animated: true) } catch { ... } ``` <br> 8๏ธโƒฃ **`NavigationCotroller` ์‚ฌ์šฉ** <br> - ๐Ÿ”’ **๋ฌธ์ œ์ ** <br> ํ™”๋ฉด ๊ฐ„์˜ ์ „ํ™˜์—์„œ `navigation` ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•˜์—ฌ `push`๋ฅผ ํ•˜์—ฌ ๋‹ค์Œ ํ™”๋ฉด์œผ๋กœ ๋„˜์–ด๊ฐ€๋„๋ก ๊ตฌ์„ฑํ•˜์˜€์Šต๋‹ˆ๋‹ค. ์ฝ”๋“œ๋ฒ ์ด์Šค๋กœ `UI`๋ฅผ ์ž‘์„ฑํ•˜๋ฉด์„œ `NavigationController`๋ฅผ ์ดˆ๊ธฐํ™”๋ฅผ ์ฝ”๋“œ๋กœ ์ง์ ‘ ํ•ด์ฃผ์–ด์•ผ ํ–ˆ์Šต๋‹ˆ๋‹ค. ๐Ÿ”‘ **ํ•ด๊ฒฐ๋ฐฉ๋ฒ•** <br> `SceneDelegate.swift` ํŒŒ์ผ์—์„œ ์•„๋ž˜์˜ ์ฝ”๋“œ๋ฅผ ์ž…๋ ฅํ•˜์˜€์Šต๋‹ˆ๋‹ค ```swift func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { //UIWindowScene์€ scene์„ ๊ด€๋ฆฌํ•˜๊ณ , UIScene์€ ์•ฑ์—์„œ ์‚ฌ์šฉ์ž ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ํ‘œ์‹œํ•˜๋Š” ์—ญํ•  //UIScene์„ ์ƒ์†๋ฐ›์€ UIWindowScene์œผ๋กœ Scene์„ ๊ด€๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด ๋‹ค์šด ์บ์ŠคํŒ…์„ ์‚ฌ์šฉ guard let windowScene = (scene as? UIWindowScene) else { return } // SceneDelegate์˜ ํ”„๋กœํผํ‹ฐ์— ํ• ๋‹น // windowScene์ด ๊ด€๋ฆฌํ•  window ์ƒ์„ฑ window = UIWindow(windowScene: windowScene) // ๋งจ ์ฒ˜์Œ ๋ณด์—ฌ์ค„ ViewController ํ• ๋‹น let mainViewController: ExpositionUniverselleViewController = ExpositionUniverselleViewController() // mainViewController๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ UINavigationController ์ดˆ๊ธฐํ™” // ๋„ค๋น„๊ฒŒ์ด์…˜์˜ ์ฒซ ํ™”๋ฉด์„ mainViewController๋กœ ์ง€์ • let navigationController: UINavigationController = UINavigationController(rootViewController : mainViewController) // ํ™”๋ฉด์˜ ์ฒซ ์‹œ์ž‘ ์ง€์ ์„ ํ• ๋‹น window?.rootViewController = navigationController // window๋ฅผ ํ™”๋ฉด์— ํ‘œ์‹œํ•˜๊ณ  key window๋กœ ์‚ฌ์šฉ window?.makeKeyAndVisible() } ``` ์œ„์—์„œ `UINavigationController`์˜ `init`์„ ํ†ตํ•ด ๋„ค๋น„๊ฒŒ์ด์…˜์˜ ์ฒซ ํ™”๋ฉด์„ ์ง€์ •ํ•˜์˜€์Šต๋‹ˆ๋‹ค. <br> 9๏ธโƒฃ **ํ™”๋ฉด์˜ ํšŒ์ „ ๊ณ ์ •** <br> - ๐Ÿ”’ **๋ฌธ์ œ์ ** <br> ์ฒซ ํ™”๋ฉด์—์„œ๋Š” `portrait mode`๋งŒ ์ง€์›ํ•˜๊ณ  ์‹ถ์—ˆ์Šต๋‹ˆ๋‹ค. ์ด ์™ธ์˜ ํ™”๋ฉด์—๋Š” ๋ชจ๋“  ํšŒ์ „์— ๋Œ€ํ•ด ์ง€์›ํ•˜๊ณ  ์‹ถ์—ˆ์Šต๋‹ˆ๋‹ค. ์ „์ฒด ์•ฑ์˜ ๋™์ž‘์— ๋Œ€ํ•œ ํ™”๋ฉด์€ ํ”„๋กœ์ ํŠธ์˜ `Deployment Info`์—์„œ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ๊ฐ๊ฐ์˜ ํ™”๋ฉด์— ๋”ฐ๋ผ ํšŒ์ „์— ๋Œ€ํ•œ ์ œํ•œ์„ ๊ฑธ๊ธฐ๋Š” ํž˜๋“ค์—ˆ์Šต๋‹ˆ๋‹ค. ๐Ÿ”‘ **ํ•ด๊ฒฐ๋ฐฉ๋ฒ•** <br> ์ฒซ ๋ฒˆ์งธ ํ™”๋ฉด์€ ์„ธ๋กœ๋กœ ๊ณ ์ •๋˜์–ด์•ผ ํ–ˆ๊ณ , ์•Œ์•„๋ณด๋˜ ์ค‘ `appDelegate`๋ฅผ ์ด์šฉํ•œ ๋ฐฉ๋ฒ•์„ ์‚ฌ์šฉํ•˜๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. `appDelegate`๋Š” ์•ฑ์˜ ๋ผ์ดํ”„ ์‚ฌ์ดํด๊ณผ ์ด๋ฒคํŠธ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฐ์ฒด๋กœ์จ ์•ฑ์˜ ์‹œ์ž‘, ์ข…๋ฃŒ ๋ฐ ์ƒํƒœ๋ณ€ํ™” ๋“ฑ๊ณผ ๊ด€๋ จ๋œ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค. `AppDelegate.swift`์— ๋‹ค์Œ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์˜€์Šต๋‹ˆ๋‹ค. ```swift // AppDelegate.swift var isOnlyPortrait: Bool = true func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask { if isOnlyPortrait { return [.portrait] } else { return [.all] } } // ExpositionUniverselleViewController.swift override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) delegate?.isOnlyPortrait = true } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) delegate?.isOnlyPortrait = false } ``` ๋งŒ์•ฝ `isOnlyPortrait`๊ฐ€ `true`์ผ ๋•Œ ์ธํ„ฐํŽ˜์ด์Šค ๋ฐฉํ–ฅ์„ ๋‚˜ํƒ€๋‚ด๋Š”`UIInterfaceOrientationMask`๊ฐ€ ์„ธ๋กœ ๋ฐฉํ–ฅ๋งŒ์„ ํ—ˆ์šฉํ•˜๋„๋ก `[.portrait]`์„ ๋ฐ˜ํ™˜ํ•˜๊ณ , false์ธ ๊ฒฝ์šฐ ๋ชจ๋“  ๋ฐฉํ–ฅ์„ ํ—ˆ์šฉํ•˜๋„๋ก `[.all]`์„ ๋ฐ˜ํ™˜ํ•˜๋„๋ก ํ•˜์˜€์Šต๋‹ˆ๋‹ค. `isOnlyPortrait`๋ฅผ ํ™”๋ฉด ๊ณ ์ •์„ ์›ํ•˜๋Š” ์ฒซ ๋ฒˆ์งธ ๋ทฐ์—์„œ ๋ทฐ๊ฐ€ ๋‚˜ํƒ€๋‚  ๋•Œ viewWillAppear์—์„œ true๋กœ ์„ค์ •ํ•ด์„œ ์„ธ๋กœ๋กœ ๊ณ ์ •๋˜๊ฒŒ ํ•˜๊ณ , ๋‹ค๋ฅธ ๋ทฐ๋กœ ๋„˜์–ด๊ฐˆ ๋•Œ viewWillDisappear์—์„œ false๋กœ ์„ค์ •ํ•ด์„œ ํ™”๋ฉด์ด ๋ชจ๋“  ๋ฐฉํ–ฅ์„ ํ—ˆ์šฉํ•˜๋„๋ก ํ•˜์˜€์Šต๋‹ˆ๋‹ค. <br> ๐Ÿ”Ÿ **`Dynamic Type`** <br> - ๐Ÿ”’ **๋ฌธ์ œ์ ** <br> 1. `Dynamic Type`์€ ์‚ฌ์šฉ์ž๊ฐ€ ์›ํ•˜๋Š” ํ…์ŠคํŠธ ์‚ฌ์ด์ฆˆ๋กœ ์ปจํ…์ธ ๋ฅผ ๋ณผ ์ˆ˜ ์žˆ๋Š” ์œ ์—ฐ์„ฑ์„ ์ œ๊ณตํ•˜๋Š” ๊ธฐ๋Šฅ์ž…๋‹ˆ๋‹ค. ์ด ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•˜์—ฌ ํ…์ŠคํŠธ์˜ ํฌ๊ธฐ๋ฅผ ํ‚ค์šฐ๊ฒŒ ๋˜๋ฉด `...`์œผ๋กœ ํ‘œ์‹œ๋˜๋Š” ๋ฌธ์ œ๊ฐ€ ์žˆ์—ˆ๊ณ , ์ด๋กœ ์ธํ•ด ์‚ฌ์šฉ์ž๊ฐ€ ํ…์ŠคํŠธ์˜ ํฌ๊ธฐ๋ฅผ ํฌ๊ฒŒ ํ–ˆ์„ ๋•Œ ๋‚ด์šฉ์„ ์•Œ ์ˆ˜ ์—†๋Š” ๋ฌธ์ œ๊ฐ€ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. 2. `Dynamic Type`์„ ์‚ฌ์šฉํ•˜์—ฌ ํ…์ŠคํŠธ์˜ ํฌ๊ธฐ๋ฅผ ํ‚ค์šธ ๋•Œ, ๋ฒ„ํŠผ์˜ ํ…์ŠคํŠธ๋Š” ์ปค์ง€์ง€๋งŒ ๋ฒ„ํŠผ ์ž์ฒด์˜ ํฌ๊ธฐ์™€ ๋ฒ„ํŠผ์ด ๋‹ด๊ธด ์Šคํƒ๋ทฐ์˜ ํฌ๊ธฐ๊ฐ€ ์ปค์ง€์ง€ ์•Š๋Š” ๋ฌธ์ œ๊ฐ€ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ๐Ÿ”‘ **ํ•ด๊ฒฐ๋ฐฉ๋ฒ•** <br> 1. `UILabel`์˜ `numberOfLines`๋Š” `UILabel`์—์„œ ํ…์ŠคํŠธ๋ฅผ ํ‘œ์‹œํ•  ์ค„์˜ ์ตœ๋Œ€ ๊ฐœ์ˆ˜๋ฅผ ์ง€์ •ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ์ด ๋˜๊ณ , ์•„๋ฌด๊ฒƒ๋„ ์ง€์ •ํ•˜์ง€ ์•Š์œผ๋ฉด ๊ธฐ๋ณธ๊ฐ’์ด `1`๋กœ ์ง€์ •๋˜๋Š” ๊ฒƒ์„ ์•Œ๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์ด ๊ฐ’์ด `1`์ผ ๋•Œ ํ…์ŠคํŠธ๊ฐ€ ํ•œ ์ค„์—๋งŒ ํ‘œ์‹œ๋˜๊ธฐ ๋•Œ๋ฌธ์— `numberOfLines = 0`์œผ๋กœ ์„ค์ •์„ ํ•ด์„œ ๋งŒ์•ฝ ๋„“์ด๊ฐ€ ์ดˆ๊ณผํ•˜๋Š” ๊ฒฝ์šฐ ์ค„๋ฐ”๊ฟˆ์ด ๋˜๋„๋ก ๋ณ€๊ฒฝํ–ˆ์Šต๋‹ˆ๋‹ค. 2. `์ด๋ฏธ์ง€ - ๋ฒ„ํŠผ - ์ด๋ฏธ์ง€`์˜ ํ˜•ํƒœ๋กœ ํ•˜๋‚˜์˜ ์Šคํƒ๋ทฐ์— ๋ฌถ์—ฌ์žˆ์—ˆ๊ณ , ์ด๋ฏธ์ง€์˜ ์ œ์•ฝ์กฐ๊ฑด์ด ๋ฒ„ํŠผ์„ ์ฐธ์กฐํ•˜๋„๋ก ํ–ˆ์Šต๋‹ˆ๋‹ค. ์ด๋Ÿฐ ๊ฒฝ์šฐ ์ด๋ฏธ์ง€์˜ ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋†’์•„์ ธ ๋ฒ„ํŠผ ํฌ๊ธฐ๊ฐ€ ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์€ ๋ฌธ์ œ์˜€์Šต๋‹ˆ๋‹ค. ์ด๋ฏธ์ง€์˜ ์ œ์•ฝ์กฐ๊ฑด์— ๋ฌธ์ œ๊ฐ€ ์žˆ์—ˆ๊ธฐ ๋•Œ๋ฌธ์— ์ด๋ฅผ ์ œ๊ฑฐํ•˜๊ณ , `setContentCompressionResistancePriority(_:for:)`๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•ด์„œ ์ด๋ฏธ์ง€๋ทฐ์˜ `CompressionResistance Priority`๋ฅผ ์กฐ์ ˆํ–ˆ์Šต๋‹ˆ๋‹ค. ```swift private let rightItemImage: UIImageView = { let imageName: String = "flag" let image: UIImage? = UIImage(named: imageName) let imageView: UIImageView = UIImageView(image: image) imageView.contentMode = .scaleAspectFit imageView.setContentCompressionResistancePriority(.fittingSizeLevel, for: .vertical) return imageView }() private let leftItemImage: UIImageView = { let imageName: String = "flag" let image: UIImage? = UIImage(named: imageName) let imageView: UIImageView = UIImageView(image: image) imageView.contentMode = .scaleAspectFit imageView.setContentCompressionResistancePriority(.fittingSizeLevel, for: .vertical) return imageView }() ``` `setContentCompressionResistancePriority(_:for:)` ๋ฉ”์„œ๋“œ๋Š” ๋ทฐ๊ฐ€ ์ ํ•ฉํ•œ ํฌ๊ธฐ๋ฅผ ์œ ์ง€ํ•˜๊ธฐ ์œ„ํ•ด ์šฐ์„ ์ˆœ์œ„๋ฅผ ์„ค์ •ํ•˜๋Š” ๋ฉ”์„œ๋“œ๋กœ, ์ฒซ ๋ฒˆ์งธ ๋งค๊ฐœ๋ณ€์ˆ˜๋Š” ๋ทฐ๊ฐ€ ์ ํ•ฉํ•œ ํฌ๊ธฐ๋ฅผ ์œ ์ง€ํ•˜๊ธฐ ์œ„ํ•œ ์šฐ์„ ์ˆœ์œ„๋ฅผ ๋‚˜ํƒ€๋‚ด๊ณ  ์ „๋‹ฌ์ธ์ž๋ฅผ ๊ธฐ๋ณธ ๊ฐ’ ๋ณด๋‹ค ๋‚ฎ์€ `.fittingSizeLevel`์œผ๋กœ ์„ค์ •ํ•ด์„œ ์ด๋ฏธ์ง€๋ทฐ์˜ ํฌ๊ธฐ์— ์Šคํƒ๋ทฐ์˜ ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋‚ฎ์•„์ง€์ง€ ์•Š๋„๋ก ํ–ˆ์Šต๋‹ˆ๋‹ค. ๋‘ ๋ฒˆ์งธ ๋งค๊ฐœ๋ณ€์ˆ˜๋Š” ์ˆ˜ํ‰ ๋ฐฉํ–ฅ, ์ˆ˜์ง ๋ฐฉํ–ฅ ๋“ฑ ํฌ๊ธฐ ์กฐ์ •์— ๋Œ€ํ•ด ์šฐ์„ ์ˆœ์œ„๋ฅผ ์„ค์ •ํ•˜๋Š” ๊ฒƒ์œผ๋กœ, ์ฒซ ๋ฒˆ์งธ ํŽ˜์ด์ง€๋Š” ์ˆ˜์ง์œผ๋กœ๋งŒ ํ‘œํ˜„๋˜๋ฉด ๋˜๊ธฐ ๋•Œ๋ฌธ์— ์ „๋‹ฌ์ธ์ž๋ฅผ`.vertical`๋กœ ์„ค์ •ํ•ด์„œ ๋ทฐ๊ฐ€ ์ˆ˜์ง ๋ฐฉํ–ฅ์œผ๋กœ ์••์ถ•๋˜๋Š” ๊ฒฝ์šฐ ๋‚ด์šฉ์ด ์ž˜๋ฆฌ์ง€ ์•Š๋„๋ก ํ–ˆ์Šต๋‹ˆ๋‹ค. <br> </br> ## ๐Ÿ“š ์ฐธ๊ณ  ๋งํฌ - [๐ŸŽApple Docs: JSONDecoder](https://developer.apple.com/documentation/foundation/jsondecoder) - [๐ŸŽApple Docs: UITableView](https://developer.apple.com/documentation/uikit/uitableview) - [๐ŸŽApple Docs: Filling a table with data](https://developer.apple.com/documentation/uikit/views_and_controls/table_views/filling_a_table_with_data) - [๐ŸŽApple Docs: Encoding and Decoding Custom Types](https://developer.apple.com/documentation/foundation/archives_and_serialization/encoding_and_decoding_custom_types) - [๐ŸŽApple Docs: Typography - Dynamic Type](https://developer.apple.com/design/human-interface-guidelines/typography#iOS-iPadOS-Dynamic-Type-sizes) - [๐ŸŽApple Docs: loadView()](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621454-loadview) - [๐Ÿ“˜stackOverflow: NSAttributedString](https://stackoverflow.com/questions/43723345/nsattributedstring-change-the-font-overall-but-keep-all-other-attributes) - [๐Ÿ“˜blog: NavigationController๋ฅผ rootViewController๋กœ ์ง€์ •ํ•˜๊ธฐ](https://vanillacreamdonut.tistory.com/277) - [๐Ÿ“˜blog: Hugging, Compression Resistance Priority](https://ios-development.tistory.com/673) - [๐Ÿ“˜blog: ์ถ”์ƒํ™” ์ˆ˜์ค€](https://velog.io/@mincho920/%ED%81%B4%EB%A6%B0%EC%BD%94%EB%93%9C-%ED%95%A8%EC%88%98%EC%9D%98-%EC%B6%94%EC%83%81%ED%99%94-%EC%88%98%EC%A4%80-%EA%B7%B8%EB%A6%AC%EA%B3%A0-Switch-%EB%AC%B8) </br> ## ๐Ÿ‘ฅ ํŒ€ ํšŒ๊ณ  - [ํŒ€ ํšŒ๊ณ  ๋งํฌ](https://github.com/idinaloq/ios-exposition-universelle/wiki/%ED%8C%80-%ED%9A%8C%EA%B3%A0) # ํŒ€ ํšŒ๊ณ  ## ์šฐ๋ฆฌํŒ€์ด ์ž˜ํ•œ ์  - ํ”„๋กœ์ ํŠธ ์ง„ํ–‰ ๊ธฐ๊ฐ„์•ˆ์— ์ต์ˆ™ํ•˜์ง€ ์•Š์€ ์ฝ”๋“œ๋ฒ ์ด์Šค๋กœ STEP3๊นŒ์ง€ ๊ตฌํ˜„ํ•œ ์  - ๋ฐœ์ƒํ•˜๋Š” ์—๋Ÿฌ์˜ ์›์ธ๊ณผ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•์„ ๋น ๋ฅด๊ฒŒ ์ฐพ์•„๋‚ธ ์  ## ์„œ๋กœ์—๊ฒŒ ์ข‹์•˜๋˜ ์  ํ”ผ๋“œ๋ฐฑ - to. idinaloq: ๊ผผ๊ผผํ•˜๊ฒŒ ์ดํ•ดํ•˜์ง€ ๋ชปํ•œ ๋ถ€๋ถ„์— ๋Œ€ํ•ด ์งš๊ณ  ๋„˜์–ด๊ฐ€๋Š” ๋ชจ์Šต์—์„œ ๋ฐฐ์šธ ์ ์ด ๋ฌด์ฒ™ ๋งŽ์•˜์Šต๋‹ˆ๋‹ค. ํŠนํžˆ ์–ด๋– ํ•œ ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•˜๊ฑฐ๋‚˜ ์‚ฌ์šฉํ•  ๋•Œ ์ด๊ฒŒ ์‹ค์ œ ์ ์šฉ์„ ํ–ˆ์„ ๋•Œ ์žฅ๋‹จ์ ์ด ๋ฌด์—‡์ธ์ง€ ๋น„๊ตํ•˜์—ฌ ํ•„์š”ํ•œ ๋ถ€๋ถ„๋งŒ์„ ์ ์šฉํ•˜๋Š” ๊ฒƒ์ด ๋งŽ์ด ๋„์›€ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. - to. hoon: ์ฝ”๋“œ๋ฒ ์ด์Šค๋กœ ์ž‘์„ฑํ•˜๋Š” ๊ฒƒ์ด ์ฒ˜์Œ์ด์—ˆ๋Š”๋ฐ, ์ด‰๋ฐ•ํ•œ ์‹œ๊ฐ„์—๋„ ๋ถˆ๊ตฌํ•˜๊ณ  ํ•˜๋ฃจ ์ •๋„ ๊ณต๋ถ€ํ•˜๋Š” ์‹œ๊ฐ„์„ ๊ฐ–์€ ๊ฒƒ์ด ๋„์›€์ด ๋งŽ์ด ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ## ์„œ๋กœ์—๊ฒŒ ํ•˜๊ณ ์‹ถ์€ ๋ง - to. idinaloq: ํŒ€์œผ๋กœ ๋งŒ๋‚˜ ์ •๋ง ์˜ค๋žœ ์‹œ๊ฐ„๊ฐ™์ด ํ”„๋กœ์ ํŠธ๋ฅผ ์ง„ํ–‰ํ•œ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค. ๊ณต๋ถ€ํ•˜๋ฉด์„œ ์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•  ๋•Œ ์ด๊ฑธ ์™œ ์‚ฌ์šฉํ•ด์•ผ์ง€๋ผ๊ณ  ์ƒ๊ฐํ•˜๋Š” ๋ชจ์Šต์„ ๋ณด๋ฉฐ ๊ผญ ๋ฐฐ์šฐ๊ณ  ์‹ถ์€ ๋ถ€๋ถ„์ด๋ผ๊ณ  ์ƒ๊ฐํ–ˆ์Šต๋‹ˆ๋‹ค. ๋‹ค์Œ์— ๋˜ ๋‹ค๋ฅธ ๋ฏธ์…˜์œผ๋กœ ๊ฐ™์ด ํ•˜๋ฉด ์ข‹๊ฒ ์Šต๋‹ˆ๋‹ค.๐Ÿ˜† - to. hoon: ๋‹ค์Œ๋ถ€ํ„ด ๋Šฆ์ง€ ์•Š๊ฒ ์Šต๋‹ˆ๋‹ค.๐Ÿ™‡โ€โ™‚๏ธ # PR: STEP 1 ## ๊ณ ๋ฏผ๋˜์—ˆ๋˜ ์  ### Assets import ํด๋” ๊ตฌ์„ฑ - ์ œ๊ณต๋ฐ›์€ expo_assets ํด๋”๋Š” ๋‚ด๋ถ€์— ๊ฐ๊ฐ์˜ ํด๋”๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” ํ˜•ํƒœ๋กœ ํด๋”๊ฐ€ ๊ตฌ์„ฑ๋˜์–ด ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. expo_assets ํด๋” ์ž์ฒด๋ฅผ import ํ•˜๋ฉด ํ›„์— ์ด๋ฏธ์ง€๋ฅผ ๋ถˆ๋Ÿฌ์˜ฌ ๋•Œ์— ํŒŒ์ผ๋ช…์œผ๋กœ ์ ‘๊ทผ ์‹œ ํด๋”๋ช…๊ณผ ํ•จ๊ป˜ ํŒŒ์ผ๋ช…์„ ์ž…๋ ฅํ•ด์•ผ ํ•˜๋Š” ๋ถ€๋ถ„์ด ์žˆ์–ด ๋‚ด๋ถ€ ํด๋”๋งŒ์„ ๊ฐœ๋ณ„์ ์œผ๋กœ import ํ•˜์˜€์Šต๋‹ˆ๋‹ค. ### Content.json - `Asset`์„ ์ถ”๊ฐ€ํ•  ๋•Œ ๋ฐ์ดํ„ฐ ํŒŒ์ผ ์™ธ์— ์ด๋ฏธ์ง€ ํŒŒ์ผ์— `Content.json`๋ผ๋Š” `json`ํฌ๋ฉง์˜ ํŒŒ์ผ์ด ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ํ•ด๋‹น ํŒŒ์ผ๋„ ์ถ”๊ฐ€๋ฅผ ํ•ด์•ผ ๋ ์ง€ ๊ณ ๋ฏผ์„ ํ•˜์˜€๋Š”๋ฐ, ์—ฌ๋Ÿฌ ๊ฐ€์ง€๋กœ ๊ฒ€์ƒ‰ํ•ด ๋ณธ ๊ฒฐ๊ณผ `Contents.json` ํŒŒ์ผ์€ `Xcode`์—์„œ `Asset Catalog`์— ํฌํ•จ๋œ ๋ฆฌ์†Œ์Šค์˜ ์ด๋ฆ„, ์œ ํ˜•, ํฌ๊ธฐ ๋ฐ ๊ธฐํƒ€ ์†์„ฑ๊ณผ ๊ด€๋ จ๋œ ์ •๋ณด๋ฅผ ํฌํ•จํ•˜๋Š” ํŒŒ์ผ์ธ ๊ฒƒ์„ ์•Œ๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ๋”ฐ๋กœ ์ถ”๊ฐ€ํ•˜์ง€๋Š” ์•Š์•˜์Šต๋‹ˆ๋‹ค. ### Decodable - `typealias Codable`์—๋Š” `Encodable`, `Decodable` ๊ธฐ๋Šฅ์ด ์žˆ์ง€๋งŒ, ์ด๋ฒˆ ํ”„๋กœ์ ํŠธ์—์„œ๋Š” ์ธ์ฝ”๋”ฉ์„ ํ•  ํ•„์š”๊ฐ€ ์—†๋‹ค๊ณ  ์ƒ๊ฐํ•ด์„œ `Decodable`์„ ์‚ฌ์šฉํ•˜์˜€์Šต๋‹ˆ๋‹ค. ### CodingKeys ์‚ฌ์šฉ - Json ํฌ๋ฉง ๋„ค์ด๋ฐ์— `_`๊ฐ€ ์žˆ์—ˆ์ง€๋งŒ, `camelCase` ์‚ฌ์šฉ์„ ์œ„ํ•ด `enum CodingKeys: String, CodingKey{...}`๋ฅผ ์ถ”๊ฐ€ํ•ด์„œ `camelCase`์— ๋งž๋Š” ์ด๋ฆ„์œผ๋กœ ๋ณ€๊ฒฝํ•˜์˜€์Šต๋‹ˆ๋‹ค. ## ์กฐ์–ธ์„ ์–ป๊ณ  ์‹ถ์€ ๋ถ€๋ถ„ ### MVC ํด๋” ๊ทธ๋ฃนํ™” - ํ˜„์žฌ MVC ํŒจํ„ด์— ๋งž์ถ”์–ด ํŒŒ์ผ์„ ๊ทธ๋ฃนํ™”ํ•˜์˜€์Šต๋‹ˆ๋‹ค. ์˜ฌ๋ผํ”„๋ผ๋ฉด ์–ด๋–ป๊ฒŒ ํŒŒ์ผ์„ ๊ตฌ๋ถ„ํ•˜์—ฌ ๋‚˜๋ˆŒ์ง€ ๊ถ๊ธˆํ•ฉ๋‹ˆ๋‹ค! MVC ๊ธฐ์ค€ - Model: ์•ฑ์—์„œ ์‚ฌ์šฉํ•˜๋Š” ๋ฐ์ดํ„ฐ์™€ ๋น„์ง€๋‹ˆ์Šค ๋กœ์ง์„ ๋‹ด๋‹นํ•ฉ๋‹ˆ๋‹ค. - View: ๋ฐ์ดํ„ฐ๋ฅผ ์‹œ๊ฐ์ ์œผ๋กœ ํ‘œํ˜„ํ•˜๋Š” ์—ญํ• ์„ ๋‹ด๋‹นํ•ฉ๋‹ˆ๋‹ค. - Controller: Model๊ณผ View ์‚ฌ์ด์˜ ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ๋ฅผ ๋‹ด๋‹นํ•ฉ๋‹ˆ๋‹ค. Model: `ExpositionUniverselle.swift` ํŒŒ์ผ๊ณผ `Item.swift` ํŒŒ์ผ์€ ๋ฐ์ดํ„ฐ์™€ ๋น„์ง€๋‹ˆ์Šค ๋กœ์ง์„ ๋‹ด๋‹นํ•˜๋Š” ๋ถ€๋ถ„์ด๋ผ๊ณ  ์ƒ๊ฐํ–ˆ์Šต๋‹ˆ๋‹ค. View: `Main.storyboard`๋Š” ์‚ฌ์šฉ์ž์—๊ฒŒ ์‹œ๊ฐ์ ์œผ๋กœ ๋ณด์ด๋Š” ๋ถ€๋ถ„์„ ๋‹ด๋‹นํ•˜๊ณ  ์žˆ๋‹ค๊ณ  ์ƒ๊ฐํ–ˆ์Šต๋‹ˆ๋‹ค. Controller: `AppDelegate.swift`์™€ `SceneDelegate.swift`๊ฐ™์€ ๊ฒฝ์šฐ๋Š” ์ง์ ‘์ ์œผ๋กœ `Model`๊ณผ `View` ์‚ฌ์ด์˜ ์ค‘๊ณ„์ž ์—ญํ• ์„ ํ•˜์ง€๋Š” ์•Š์ง€๋งŒ `MVC`ํŒจํ„ด์œผ๋กœ ๊ตฌ๋ถ„ํ•œ๋‹ค๋ฉด ์ปจํŠธ๋กค๋Ÿฌ ์—ญํ• ์— ์ข€ ๋” ๊ฐ€๊น๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ ‡๊ฒŒ ์ƒ๊ฐํ•œ ์ด์œ ๋Š” `AppDelegate`๊ฐ™์€ ๊ฒฝ์šฐ ํŠน์ •ํ•œ `scenes`, `views`, `view controllers`์— ํ•œ์ •๋˜์ง€ ์•Š๊ณ  ์•ฑ ์ž์ฒด๋ฅผ ํƒ€๊ฒŸํ•˜๋Š” ์ด๋ฒคํŠธ์— ๋Œ€์‘ํ•˜๋Š” ์—ญํ• ์„ ์ˆ˜ํ–‰ํ•˜๋ฉฐ, `SceneDelegate`์€ `UI` ์ƒํƒœ์— ๋”ฐ๋ฅธ ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ๋ฅผ ๋‹ด๋‹นํ•˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. `let`์œผ๋กœ ์ˆ˜์ •ํ•˜์˜€์Šต๋‹ˆ๋‹ค. ๊ณต์‹ ๋ฌธ์„œ์—์„œ ์˜ˆ์‹œ๋กœ ์žˆ๋Š” ์ƒ˜ํ”Œ ์ฝ”๋“œ๋Š” `var`๋ฅผ ์‚ฌ์šฉํ•˜์˜€์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์ €ํฌ๊ฐ€ ์ฐพ์•„๋ณธ ๋‚ด์šฉ์œผ๋กœ๋Š” ํ›„์— ๊ฐ’์ด ๋ณ€๊ฒฝ๋  ์ผ์ด ์—†๋Š” ๊ฒฝ์šฐ์—๋Š” `let`์„ ์‚ฌ์šฉํ•˜์—ฌ ์•ˆ์ •์„ฑ์„ ๋†’์ผ ์ˆ˜ ์žˆ๋‹ค๊ณ  ์ƒ๊ฐํ•˜์˜€์Šต๋‹ˆ๋‹ค. ๊ณต์‹ ๋ฌธ์„œ์˜ ์˜ˆ์‹œ ์ฝ”๋“œ์—๋Š” ์ดํ›„์— ๋ณ€๊ฒฝ์— ๋Œ€ํ•œ ์˜ˆ์‹œ๋Š” ์—†์ง€๋งŒ `var`๋ฅผ ์‚ฌ์šฉํ•ด ๋ณ€์ˆ˜๋กœ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋Š”๋ฐ ์—ฌ๊ธฐ์„œ ์ด๋ ‡๊ฒŒ ์‚ฌ์šฉํ•˜๋Š” ์ด์œ ๊ฐ€ ์žˆ์„๊นŒ์š”? ์˜ˆ์‹œ ์ฝ”๋“œ: [JSONDecoder](https://developer.apple.com/documentation/foundation/jsondecoder) ์•ˆ๋…•ํ•˜์„ธ์š” ์˜ฌ๋ผํ”„(@1Consumption)๐Ÿ˜Š hoon, idinaloq์ž…๋‹ˆ๋‹ค. ๋งŒ๊ตญ๋ฐ•๋žŒํšŒ STEP 2 PR ๋“œ๋ฆฝ๋‹ˆ๋‹ค. ์ฒ˜์Œ์œผ๋กœ ์ฝ”๋“œ ๋ฒ ์ด์Šค๋กœ UI๋ฅผ ๊ตฌํ˜„ํ•˜๋ฉฐ ๋งŽ์€ ์–ด๋ ค์›€์ด ์žˆ์—ˆ๊ณ  ์—ฌ์ „ํžˆ ๋ถ€์กฑํ•œ ์ ์ด ๋งŽ์€ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค. ์ฒœ์ฒœํžˆ ๋ณด์‹œ๊ณ  ์ฝ”๋ฉ˜ํŠธ ๋‚จ๊ฒจ์ฃผ์‹œ๋ฉด ์—ด์‹ฌํžˆ ๊ณต๋ถ€ํ•ด์„œ ๋‹ค์‹œ ๋‹ต๋ณ€ ๋‚จ๊ธฐ๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. ๊ฐ์‚ฌํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.๐Ÿ™‡ ์ข‹์€ ์ฃผ๋ง ๋ณด๋‚ด์„ธ์š”.๐Ÿฅณ # PR: STEP 2 ## ๊ณ ๋ฏผ๋˜์—ˆ๋˜ ์  ### ์˜คํ† ๋ ˆ์ด์•„์›ƒ ์‚ฌ์šฉ - ์Šคํ† ๋ฆฌ๋ณด๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ  ์ฝ”๋“œ ๋ฒ ์ด์Šค๋กœ ๋ทฐ๋ฅผ ์ƒ์„ฑํ•˜์—ฌ `addSubview`๋ฅผ ํ†ตํ•ด ๋ทฐ๋ฅผ ์ถ”๊ฐ€ํ•˜๋ฉด ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ์— ๋‚˜์˜ค์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. ์ƒ์„ฑํ•œ ๋ทฐ๋ฅผ ํ™”๋ฉด์— ์–ด๋А ์œ„์น˜์— ๋„์›Œ์ฃผ์–ด์•ผ ํ•˜๋Š”์ง€ ํŒ๋‹จํ•˜์ง€ ๋ชปํ•˜์—ฌ ์ƒ๊ธด ๋ฌธ์ œ์˜€์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ํ•ด๊ฒฐํ•ด ์ฃผ๊ธฐ ์œ„ํ•ด ์˜คํ† ๋ ˆ์ด์•„์›ƒ์„ ์‚ฌ์šฉํ•˜์—ฌ ํ™”๋ฉด์˜ ์–ด๋А ์œ„์น˜์— ๋ทฐ๋ฅผ ๋„์šธ์ง€์— ๋Œ€ํ•œ ์ œ์•ฝ์กฐ๊ฑด์„ ์„ค์ •ํ•˜์˜€์Šต๋‹ˆ๋‹ค. ```swift private func configureExpositionUniverselleScrollViewConstraint() { NSLayoutConstraint.activate([ expositionUniverselleScrollView.topAnchor.constraint(equalTo: view.topAnchor), expositionUniverselleScrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor), expositionUniverselleScrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor), expositionUniverselleScrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor), ]) } private func configureContentStackViewConstraint() { NSLayoutConstraint.activate([ contentStackView.topAnchor.constraint(equalTo: expositionUniverselleScrollView.topAnchor), contentStackView.bottomAnchor.constraint(equalTo: expositionUniverselleScrollView.bottomAnchor), contentStackView.widthAnchor.constraint(equalTo: expositionUniverselleScrollView.widthAnchor) ]) } ``` ### ๋ทฐ ์—…๋ฐ์ดํŠธ ์‹œ์  - ๋ทฐ ์ปจํŠธ๋กค๋Ÿฌ์—์„œ ์—ฐ์‚ฐ ํ”„๋กœํผํ‹ฐ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ทฐ๋ฅผ ์ƒ์„ฑํ•  ๋•Œ `UILabel`์˜ ๊ฒฝ์šฐ ์ž…๋ ฅ๋˜์–ด์•ผ ํ•˜๋Š” ๋‚ด์šฉ๋“ค์„ ํ•จ๊ป˜ ์ž…๋ ฅํ•˜์—ฌ ์ƒ์„ฑํ•˜์˜€์Šต๋‹ˆ๋‹ค. ```swift private var expositionUniverselle: ExpositionUniverselle? private lazy var titleLabel: UILabel = { let label: UILabel = UILabel() label.text = = expositionUniverselle?.title label.textAlignment = .center return label }() ``` ์œ„์™€ ๊ฐ™์ด ์‚ฌ์šฉํ•˜๋ฉด `expositionUniverselle`์ด ์ดˆ๊ธฐํ™” ์ „์— ๊ฐ’์— ์ ‘๊ทผํ•˜์—ฌ `title` ๊ฐ’์„ ๋ฐ›์•„์˜ค๋ ค๊ณ  ํ•˜๊ธฐ ๋•Œ๋ฌธ์— `lazy var`๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ–ˆ์Šต๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ทฐ๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ์ดํ›„์— `updateLabel()` ๋ฉ”์„œ๋“œ์—์„œ `label.text`์— ๊ฐ’์„ ํ• ๋‹นํ•˜๋Š” ์ž‘์—…๋“ค์„ ๋ชจ์•„์„œ ์ง„ํ–‰ํ•˜๋„๋ก ํ•˜์˜€์Šต๋‹ˆ๋‹ค. ```swift private var expositionUniverselle: ExpositionUniverselle? private let titleLabel: UILabel = { let label: UILabel = UILabel() label.textAlignment = .center return label }() ... private func updateLabel() { navigationItem.title = "๋ฉ”์ธ" titleLabel.text = expositionUniverselle?.titleForLabel visitorsLabel.text = expositionUniverselle?.visitorsForLabel locationLabel.text = expositionUniverselle?.locationForLabel durationLabel.text = expositionUniverselle?.durationForLabel totalDescriptionLabel.text = expositionUniverselle?.totalDescription } ``` ### `addArrangedSubview()` ๋ฉ”์„œ๋“œ ์ค‘๋ณต ์ œ๊ฑฐ - `ExpositionUniverselleViewController`์˜ ์ฒซ ํ™”๋ฉด์—์„œ๋Š” ๋‹ค์–‘ํ•œ ๋ทฐ๋“ค์„ ์‚ฌ์šฉํ•˜์—ฌ ์ „์ฒด ํ™”๋ฉด์„ ๊ตฌ์„ฑํ•˜์˜€์Šต๋‹ˆ๋‹ค. ๋งŽ์€ ๋ทฐ๋“ค์„ `contentStackView`์— ์ถ”๊ฐ€ํ•˜์—ฌ ๊ด€๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด ์•„๋ž˜์™€ ๊ฐ™์ด ์—ฌ๋Ÿฌ ๋ฒˆ์˜ ๋™์ผํ•œ `addArrangedSubview()` ๋ฉ”์„œ๋“œ๋ฅผ ์ž‘์„ฑํ•ด์•ผ ํ–ˆ์Šต๋‹ˆ๋‹ค. ```swift override func viewDidLoad() { super.viewDidLoad() decodeExpositionUniverselle() horizontalStackView.addArrangedSubview(leftFlagImage) horizontalStackView.addArrangedSubview(expositionItemListButton) horizontalStackView.addArrangedSubview(rightFlagImage) verticalStackView.addArrangedSubview(titleLabel) verticalStackView.addArrangedSubview(posterImage) verticalStackView.addArrangedSubview(visitorsLabel) verticalStackView.addArrangedSubview(durationLabel) verticalStackView.addArrangedSubview(locationLabel) verticalStackView.addArrangedSubview(totalDescriptionLabel) verticalStackView.addArrangedSubview(horizontalStackView) expositionUniverselleScrollView.addSubview(verticalStackView) view.addSubview(expositionUniverselleScrollView) } ``` `addArrangedSubview()` ๋ฉ”์„œ๋“œ์— ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ์ฃผ์–ด์ง€๋Š” ๋ทฐ๋งŒ ๋ณ€๊ฒฝํ•˜๋ฉด ์ค‘๋ณต๋œ ์ฝ”๋“œ๋ฅผ ์ค„์ผ ์ˆ˜ ์žˆ์–ด ๊ฐ€๋…์„ฑ์„ ๋†’์ผ ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ```swift private func configureView() { addArrangedSubviews(to: buttonStackView, elements: [leftItemImage, expositionItemListButton, rightItemImage ]) addArrangedSubviews(to: contentStackView, elements: [titleLabel, posterImage, visitorsLabel, locationLabel, durationLabel, totalDescriptionLabel, buttonStackView]) expositionUniverselleScrollView.addSubview(contentStackView) view.addSubview(expositionUniverselleScrollView) } private func addArrangedSubviews(to superview: UIStackView, elements subviews: [AnyObject]) { subviews.forEach { guard let subview = $0 as? UIView else { return } superview.addArrangedSubview(subview) } } ``` ์œ„์™€ ๊ฐ™์ด `addArrangedSubviews()` ๋ฉ”์„œ๋“œ์— ์—ฌ๋Ÿฌ ๋ทฐ๋“ค์„ `Array`๋กœ ๋ฌถ์–ด ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ๋„˜๊ธฐ๋ฉด ์ˆœ์ฐจ์ ์œผ๋กœ `superview`์— `subview`๋“ค์ด ์ถ”๊ฐ€๋˜๋„๋ก ํ•˜์˜€์Šต๋‹ˆ๋‹ค. ### ๋งค์ง๋ฆฌํ„ฐ๋„ ์ตœ์†Œํ™” - `exposition_universelle_1900`, `items`๊ฐ™์€ `JSON` ํŒŒ์ผ์—์„œ ์ง์ ‘์ ์œผ๋กœ ํŒŒ์ผ์„ ์ฝ์–ด์˜ฌ ๋•Œ ์•„๋ž˜์™€ ๊ฐ™์ด ํŒŒ์ผ ์ด๋ฆ„์„ ์ง์ ‘ ์ง€์ •ํ•˜์—ฌ ์ฝ์–ด์˜ค๋„๋ก ํ•˜์˜€์Šต๋‹ˆ๋‹ค. ```swift private func decodeExpositionUniverselle() { do { expositionUniverselle = try JSONDecoder().decode(ExpositionUniverselle.self, from: "exposition_universelle_1900") } catch { print(error) } } ``` ์ถ”ํ›„ ํŒŒ์ผ์˜ ์ด๋ฆ„์ด๋‚˜ ํŒŒ์ผ ์ž์ฒด๊ฐ€ ๋ฐ”๋€Œ๋Š” ๊ฒฝ์šฐ ํŒŒ์ผ ์ด๋ฆ„์„ ๊ณ„์† ๋ณ€๊ฒฝํ•ด์•ผ ๋˜๋Š”๋ฐ, ๋งŒ์•ฝ ๋ทฐ ์ปจํŠธ๋กค๋Ÿฌ๊ฐ€ ์ปค์ง€๊ฑฐ๋‚˜ ํŒŒ์ผ์ด ๋งŽ์•„์ง€๋Š” ๊ฒฝ์šฐ ์ผ์ผ์ด ํŒŒ์ผ๋ช…์„ ์ž…๋ ฅํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ์ฐพ์•„ ๋ณ€๊ฒฝํ•˜๊ธฐ ์–ด๋ ค์šธ ๊ฒƒ ๊ฐ™๋‹ค๊ณ  ์ƒ๊ฐํ–ˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด `JSONFile.swift` ํŒŒ์ผ์„ ๋งŒ๋“ค๊ณ  ํŒŒ์ผ ์ด๋ฆ„์„ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋„๋ก ํ–ˆ์Šต๋‹ˆ๋‹ค. ```swift enum JSONFile { case exposition case items var name: String { switch self { case .exposition: return "exposition_universelle_1900" case .items: return "items" } } } ``` ์ด๋ ‡๊ฒŒ ํ•˜๋‚˜์˜ ํŒŒ์ผ์—์„œ JSON ํŒŒ์ผ์„ ๊ด€๋ฆฌํ•˜๊ธฐ ๋•Œ๋ฌธ์— ํŒŒ์ผ์˜ ์ˆ˜์ •์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ ํŒŒ์ผ ๋ช…์„ ์ž…๋ ฅํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ์ฐพ์•„๊ฐˆ ํ•„์š” ์—†์ด `JSONFile.swift` ํŒŒ์ผ์—์„œ ๋ชจ๋‘ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ์œ„์˜ `JSONFile` ํƒ€์ž…์„ ์ ์šฉํ•˜์—ฌ ์•„๋ž˜์™€ ๊ฐ™์ด ์ฝ”๋“œ๋ฅผ ์ˆ˜์ •ํ•˜์˜€์Šต๋‹ˆ๋‹ค. ```swift private func decodeExpositionUniverselle() { do { expositionUniverselle = try JSONDecoder().decode(ExpositionUniverselle.self, from: JSONFile.exposition.name) } catch { print(error) } } ``` ### ์„ธ ๋ฒˆ์งธ ๋ทฐ๋กœ ๋ฐ์ดํ„ฐ ๋„˜๊ธฐ๊ธฐ - ๋‘ ๋ฒˆ์งธ ๋ทฐ ์ปจํŠธ๋กค์ธ `ExpositionItemViewController`์—์„œ `JSON` ๋ฐ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ `decodeItems()` ๋ฉ”์„œ๋“œ๋ฅผ ํ†ตํ•ด ๋””์ฝ”๋”ฉ ๋œ ๋ฐ์ดํ„ฐ๋ฅผ `itemList`์— ํ• ๋‹นํ•ฉ๋‹ˆ๋‹ค. ์„ธ ๋ฒˆ์งธ ๋ทฐ ์ปจํŠธ๋กค์ธ `ExpositionItemListViewController`์—์„œ๋„ ์œ„์™€ ๊ฐ™์ด ๋””์ฝ”๋”ฉ์„ ํ•˜๋ฉด ๊ฐ™์€ ๊ฒฐ๊ณผ๊ฐ€ ๋‚˜์˜ค์ง€๋งŒ, ๋™์ผํ•œ ํŒŒ์ผ์„ ๋‘ ๋ฒˆ ๋””์ฝ”๋”ฉ ํ•˜๋Š” ๊ฒƒ๋ณด๋‹ค ๋‘ ๋ฒˆ์งธ ๋ทฐ ์ปจํŠธ๋กค์—์„œ ๋‹ค์Œ ๋ทฐ ์ปจํŠธ๋กค๋Ÿฌ์— ๋ฐ์ดํ„ฐ๋ฅผ ๋„˜๊ฒจ์ฃผ๋Š” ํŽธ์ด ๋™์ผํ•œ ์ž‘์—…์„ ์—ฌ๋Ÿฌ ๋ฒˆ ๋ฐ˜๋ณตํ•˜์ง€ ์•Š์•„ ์ข‹๋‹ค๊ณ  ์ƒ๊ฐํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— ์•„๋ž˜์™€ ๊ฐ™์ด ๊ตฌํ˜„ํ–ˆ์Šต๋‹ˆ๋‹ค. ```swift func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) let expositionItemViewController: ExpositionItemViewController = ExpositionItemViewController(item: itemList[indexPath.row]) navigationController?.pushViewController(expositionItemViewController, animated: true) } ``` ### `JSONDecoder Extension` - ๊ฐ™์€ `decode()` ๋ฉ”์„œ๋“œ๋ฅผ ํ˜„์žฌ ๋‘ ๋ฒˆ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋Š”๋ฐ, `do - try - catch` ๊ณผ์ •์—์„œ `NSDataAsset`์„ ์˜ต์…”๋„ ๋ฐ”์ธ๋”ฉ์„ ํ•ด์„œ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. `decode()` ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ํšŸ์ˆ˜๊ฐ€ ๋Š˜์–ด๋‚œ๋‹ค๋ฉด, `NSDataAsset`์— ๋Œ€ํ•œ ์˜ต์…”๋„ ๋ฐ”์ธ๋”ฉ ์ฝ”๋“œ๊ฐ€ ์ค‘๋ณต๋˜๋Š” ๊ฒƒ ๊ฐ™๋‹ค๊ณ  ์ƒ๊ฐํ•˜์—ฌ `Extension`์œผ๋กœ `decode`์˜ ๊ธฐ๋Šฅ์„ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ํ™•์žฅํ•˜์—ฌ ์‚ฌ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค. ```swift extension JSONDecoder { func decode<T: Decodable>(_ type: T.Type, from assetName: String) throws -> T { guard let json = NSDataAsset(name: assetName) else { throw NSDataAssetError.invalidDataAsset } return try JSONDecoder().decode(type, from: json.data) } } ``` ### `CustomCell` ์‚ฌ์šฉ - ๋‘ ๋ฒˆ์งธ ๋ทฐ์—์„œ ๊ฐ๊ฐ์˜ ์…€์ด `subtitle`์„ ํ•„์š”๋กœ ํ–ˆ์Šต๋‹ˆ๋‹ค. ```swift cell.detailTextLabel?.text = "Subtitle" ``` ์œ„์™€ ๊ฐ™์ด ์„ ์–ธํ•ด์„œ `subtitle`์„ ์‚ฌ์šฉํ–ˆ๋Š”๋ฐ, ๋ทฐ์—๋Š” ์ ์šฉ๋˜์ง€ ์•Š๋Š” ๋ฌธ์ œ๊ฐ€ ์žˆ์—ˆ๊ณ , ์ฝ”๋“œ ๋ฒ ์ด์Šค๋กœ ์ž‘์„ฑํ•ด์„œ ์ด๋Ÿฌํ•œ ๋ฌธ์ œ๊ฐ€ ์žˆ๋‹ค๊ณ  ์ƒ๊ฐํ–ˆ์Šต๋‹ˆ๋‹ค. ์ปค์Šคํ…€ ์…€์„ ์‚ฌ์šฉํ•ด์„œ `style`์„ `subtitle`๋กœ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์„ค์ •ํ–ˆ์Šต๋‹ˆ๋‹ค. ```swift final class ItemUITableViewCellStyleSubtitle: UITableViewCell { override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: .subtitle, reuseIdentifier: reuseIdentifier) accessoryType = .disclosureIndicator } ... } ``` ## ํ•ด๊ฒฐ์ด ๋˜์ง€ ์•Š์€ ์  ### `massive viewController` - ์Šคํ† ๋ฆฌ๋ณด๋“œ๋กœ `UI`๋ฅผ ๊ตฌํ˜„ํ•  ๋•Œ ์•„์ด์ฝ˜์„ ์Šคํ† ๋ฆฌ๋ณด๋“œ์— ์ƒ์„ฑํ•˜๋Š” ์ˆœ๊ฐ„ `UI`๊ฐ€ ์ƒ์„ฑ๋˜๊ณ , ๊ทธ๊ฒƒ์„ `ViewController`์™€ ๊ฐ๊ฐ์˜ `UI`๋“ค์„ ์—ฐ๊ฒฐํ•ด ์ฃผ๋Š” ์ž‘์—…์„ ์ง„ํ–‰ํ–ˆ์—ˆ์Šต๋‹ˆ๋‹ค. ์ฝ”๋“œ ๋ฒ ์ด์Šค๋กœ ๋ณ€๊ฒฝ๋œ ์ง€๊ธˆ์˜ ์ฝ”๋“œ๋ฅผ ๋ณด๋ฉด, `ViewController`์—์„œ `UI`๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ์—ฐ๊ฒฐ๊นŒ์ง€ ํ•˜๋Š” ๋‘ ๊ฐœ์˜ ๋™์ž‘์„ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— UI๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๋ถ€๋ถ„์€ `View`์— ๋“ค์–ด๊ฐ€์•ผ ๋œ๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ๊ณผ์ •๋“ค์ด ์–ด๋–ป๊ฒŒ ๋‚˜๋ˆ ์ง€๊ณ , ์‹คํ–‰๋˜์–ด์•ผ ํ•˜๋Š”์ง€์— ๋Œ€ํ•ด ์ž˜ ์•Œ์ง€ ๋ชปํ•˜์—ฌ `ViewController`์— ๋ชจ๋‘ ๊ตฌํ˜„ํ•œ ์ƒํƒœ๋กœ ์ด ๋ถ€๋ถ„์„ ์•„์ง ํ•ด๊ฒฐํ•˜์ง€ ๋ชปํ•˜์˜€์Šต๋‹ˆ๋‹ค. - ๊ณ ๋ฏผํ•˜์‹  ์ ์— ์ ์–ด์ฃผ์‹  ์•„์ด์ฝ˜์„ ์Šคํ† ๋ฆฌ๋ณด๋“œ์— ์ƒ์„ฑํ•˜๋Š” ์ˆœ๊ฐ„ UI๊ฐ€ ์ƒ์„ฑ๋˜๊ณ , ๋ผ๋Š” ๋ถ€๋ถ„์ด ์–ด๋–ค ๊ฒƒ์„ ๋œปํ•˜๋Š”์ง€ ์ž˜ ๋ชจ๋ฅด๊ฒ ์Šต๋‹ˆ๋‹ค. ์ด ๋ถ€๋ถ„์„ ์กฐ๊ธˆ ๋” ์ž์„ธํ•˜๊ฒŒ ์„ค๋ช…ํ•ด ์ฃผ์‹œ๊ฒ ์–ด์š”? - ์Šคํ† ๋ฆฌ๋ณด๋“œ๋กœ UI๋ฅผ ๊ตฌํ˜„ํ•  ๋•Œ ์•„์ด์ฝ˜์„ ์Šคํ† ๋ฆฌ๋ณด๋“œ์— ์ถ”๊ฐ€ํ•˜๋ฉด XML์—๋„ ์ถ”๊ฐ€๊ฐ€ ๋˜์–ด UI์— ๋Œ€ํ•œ ์ •๋ณด๊ฐ€ ๊ธฐ๋ก๋œ๋‹ค๊ณ  ์ƒ๊ฐํ–ˆ์Šต๋‹ˆ๋‹ค. - - ์—ฌ๊ธฐ ์žˆ๋Š” ์ฝ”๋“œ๋“ค์˜ ์ถ”์ƒํ™” ์ˆ˜์ค€์ด ์•ˆ๋งž๋„ค์š”. ์—ญํ• ๋ณ„๋กœ ๋ฉ”์†Œ๋“œ๋กœ ๋ถ„๋ฆฌํ•ด๋ณด์‹œ๊ฒ ์–ด์š”? - clean code์—์„œ ์ถ”์ƒํ™” ๋ ˆ๋ฒจ ๊ณต๋ถ€ํ•ด๋ณด๊ธฐ - print๋ฅผ ํ•˜๋ฉด ์•ฑ์„ ์‚ฌ์šฉํ•˜๋Š” ์‚ฌ์šฉ์ž๋“ค์€ ์—๋Ÿฌ ๋ฐœ์ƒ ์ƒํ™ฉ์„ ์ธ์ง€ํ•˜์ง€ ๋ชปํ•ฉ๋‹ˆ๋‹ค. ์–ด๋–ค ๋ฐฉ๋ฒ•์ด ์žˆ์„๊นŒ์š”? - ์–ผ๋Ÿฟ์œผ๋กœ ํ‘œ์‹œํ•˜๋„๋ก ์ˆ˜์ •ํ•˜๊ธฐ - ์ด ์ฝ”๋“œ์˜ ์—ญํ• ์€ ๋ญ”๊ฐ€์š”? ์™œ ํ•ด์ฃผ์…จ๋‚˜์š”? - `translatesAutoresizingMaskIntoConstraints`์˜ [๊ณต์‹ ๋ฌธ์„œ](https://developer.apple.com/documentation/uikit/uiview/1622572-translatesautoresizingmaskintoco)๋ฅผ ๋ณด๋ฉด ๋ทฐ์˜ `Autoresizing mask`๋ฅผ `Auto Layout constraints`๋กœ ๋ฐ”๊ฟ€์ง€๋ฅผ ๊ฒฐ์ •ํ•˜๋Š” `Boolean` ๊ฐ’์ด๋ผ๊ณ  ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค. `translatesAutoresizingMaskIntoConstraints`์˜ ๊ฐ’์ด `true` ์ผ ๋•Œ ์‹œ์Šคํ…œ์€ ๋ทฐ์˜ `Autoresizing mask`์— ์˜ํ•ด ์ง€์ •๋œ ์ œ์•ฝ ์กฐ๊ฑด์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ๊ฒฝ์šฐ์— ์ƒˆ๋กœ์šด ์ œ์•ฝ ์กฐ๊ฑด์„ ์ถ”๊ฐ€ํ•˜๋ฉด ์ด์ „ ์ œ์•ฝ ์กฐ๊ฑด๊ณผ ์ถฉ๋Œ์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด `translatesAutoresizingMaskIntoConstraints`์˜ ๊ฐ’์„ `false`๋กœ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค. - ํด๋กœ์ € ์ƒ์„ฑ์„ ํ†ตํ•ด ์ธ์Šคํ„ด์Šค๋ฅผ ๋งŒ๋“ค์–ด์ค€ ์ด์œ ๋Š” ๋ญ”๊ฐ€์š”? ์ด๋ ‡๊ฒŒํ•˜๋ฉด ์–ด๋–ค ์žฅ์ ์ด ์žˆ๋˜๊ฐ€์š”? - ํด๋กœ์ €๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ธ์Šคํ„ด์Šค๋ฅผ ์ƒ์„ฑํ•จ์œผ๋กœ์จ ๊ด€๋ จ๋œ ์ฝ”๋“œ๋ฅผ ๋ธ”๋กํ™”ํ•˜์—ฌ ๊ฐ€๋…์„ฑ์„ ๋†’์ผ ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ๋ทฐ๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ์ด์— ํ•„์š”ํ•œ ํ”„๋กœํผํ‹ฐ๋“ค์„ ํ•จ๊ป˜ ์„ค์ •ํ•จ์œผ๋กœ์จ ํ•˜๋‚˜์˜ ์ฝ”๋“œ ๋ธ”๋ก ์•ˆ์—์„œ ํ•„์š”ํ•œ ๋ชจ๋“  ๋™์ž‘์„ ์ˆ˜ํ–‰ํ•ด ๊ฐ€๋…์„ฑ์„ ๋†’์˜€์Šต๋‹ˆ๋‹ค. - ํ•˜๋‚˜ํ•˜๋‚˜ ์ ‘๊ทผํ•˜์ง€๋ง๊ณ  ๋ฉ”์†Œ๋“œ ํ•˜๋‚˜๋กœ ์ ‘๊ทผ์„ ์ œ์–ดํ•ด๋ณด๋Š”๊ฑด ์–ด๋–จ๊นŒ์š”? - `configureCell(with:item:)` ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์…€์— ์ ‘๊ทผํ•˜๋„๋ก ์ˆ˜์ •ํ•˜์˜€์Šต๋‹ˆ๋‹ค. - ๋‚ด๋ถ€์—์„œ ์…€์— ๊ฐ๊ฐ์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•˜๋„๋ก ์ˆ˜์ •ํ•˜์˜€์Šต๋‹ˆ๋‹ค. - final์„ ๋ถ™์—ฌ์ค€ ์ด์œ ๊ฐ€ ๊ถ๊ธˆํ•ฉ๋‹ˆ๋‹ค. ์ด ํ‚ค์›Œ๋“œ๋Š” ์–ด๋–ค ์—ญํ• ์„ ํ•˜๋‚˜์š”? - [final](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/inheritance/#Preventing-Overrides) ํ‚ค์›Œ๋“œ๋Š” ๋” ์ด์ƒ ์ƒ์†์ด ํ•„์š” ์—†์Œ์„ ๋ช…์‹œ์ ์œผ๋กœ ํ‘œํ˜„ํ•˜๋ฉฐ, ์˜ค๋ฒ„๋ผ์ด๋“œ ํ•  ์‹œ ์ปดํŒŒ์ผ ์—๋Ÿฌ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ```swift required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } ``` - ์ด ์ด๋‹ˆ์…œ๋ผ์ด์ €๋Š” ์–ธ์ œ ๋ถˆ๋ฆฌ๋‚˜์š”? fatalError๋ฅผ ํ•ด์ค€ ์˜๋„๊ฐ€ ๊ถ๊ธˆํ•ฉ๋‹ˆ๋‹ค. - ์ƒ์†๋ฐ›์€ `init()` ๋ฉ”์„œ๋“œ ๋Œ€์‹  `ExpositionItemViewController`์—์„œ ์ง€์ • ์ƒ์„ฑ์ž๋ฅผ ์ง์ ‘ ๊ตฌํ˜„ํ–ˆ์Šต๋‹ˆ๋‹ค. ํ˜„์žฌ ์ฝ”๋“œ๋ฒ ์ด์Šค๋ฅผ ํ†ตํ•ด ์ดˆ๊ธฐํ™”๋ฅผ ์ง„ํ–‰ํ•˜๊ณ  ์žˆ๊ณ , ์Šคํ† ๋ฆฌ๋ณด๋“œ๋‚˜ xib ํŒŒ์ผ์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ๋Š” ์—†๊ธฐ ๋•Œ๋ฌธ์— `required init?` ๋ฉ”์„œ๋“œ๊ฐ€ ์ง์ ‘ ํ˜ธ์ถœ๋  ์ผ์€ ์—†๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค. ๋งŒ์•ฝ ํ˜ธ์ถœ๋œ๋‹ค๋ฉด ๋น„์ •์ƒ์ ์ธ ์ž‘๋™์ด๊ธฐ ๋•Œ๋ฌธ์— `fatalError`๋ฅผ ํ†ตํ•ด ์•ฑ์ด ์‹คํ–‰ ์ข…๋ฃŒ๋˜๋„๋ก ํ–ˆ์Šต๋‹ˆ๋‹ค. - ์ด ์ฝ”๋“œ์˜ ์—ญํ• ์ด ๊ถ๊ธˆํ•ฉ๋‹ˆ๋‹ค. ํ•œ์ค„ํ•œ์ค„ ์„ค๋ช…ํ•ด์ฃผ์‹œ๊ฒ ์–ด์š”? - ์ด ์ฝ”๋“œ๋Š” ๋„ค์ด๊ฒŒ์ด์…˜ ์ปจํŠธ๋กค๋Ÿฌ๋ฅผ ๋ฃจํŠธ๋ทฐ ์ปจํŠธ๋กค๋Ÿฌ๋กœ ์ง€์ •ํ•ด์ฃผ๋Š” ์—ญํ• ์ž…๋‹ˆ๋‹ค. ```swift //UIWindowScene์€ scene์„ ๊ด€๋ฆฌํ•˜๊ณ , UIScene์€ ์•ฑ์—์„œ ์‚ฌ์šฉ์ž ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ํ‘œ์‹œํ•˜๋Š” ์—ญํ•  //UIScene์„ ์ƒ์†๋ฐ›์€ UIWindowScene์œผ๋กœ Scene์„ ๊ด€๋ฆฌํ•˜๊ธฐ์œ„ํ•ด ๋‹ค์šด์บ์ŠคํŒ…์„ ์‚ฌ์šฉ guard let windowScene = (scene as? UIWindowScene) else { return } // SceneDelegate์˜ ํ”„๋กœํผํ‹ฐ์— ํ• ๋‹น // windowScene์ด ๊ด€๋ฆฌํ•  window ์ƒ์„ฑ window = UIWindow(windowScene: windowScene) // ๋งจ ์ฒ˜์Œ ๋ณด์—ฌ์ค„ ViewController ํ• ๋‹น let mainViewController = ExpositionUniverselleViewController() // mainViewController๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ UINavigationController ์ดˆ๊ธฐํ™” // ๋„ค๋น„๊ฒŒ์ด์…˜์˜ ์ฒซ ํ™”๋ฉด์„ mainViewController๋กœ ์ง€์ • let navigationController = UINavigationController(rootViewController : mainViewController) // ํ™”๋ฉด์˜ ์ฒซ ์‹œ์ž‘์ง€์ ์„ ํ• ๋‹น window?.rootViewController = navigationController // window๋ฅผ ํ™”๋ฉด์— ํ‘œ์‹œํ•˜๊ณ  key window๋กœ ์‚ฌ์šฉ window?.makeKeyAndVisible() } ``` ์•ˆ๋…•ํ•˜์„ธ์š” ์˜ฌ๋ผํ”„()๐Ÿ˜Š ๋งˆ์ง€๋ง‰ PR ๋“œ๋ฆฝ๋‹ˆ๋‹ค. 2์ฃผ๊ฐ„ ์ •์‹ ์—†์ด ์ •๋ง ๋น ๋ฅด๊ฒŒ ์‹œ๊ฐ„์ด ํ˜๋Ÿฌ๊ฐ„ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค. ์ฝ”๋ฉ˜ํŠธ ๋‚จ๊ฒจ์ฃผ์‹œ๋ฉด ๋งˆ์ง€๋ง‰๊นŒ์ง€ ์—ด์‹ฌํžˆ ๋งˆ๋ฌด๋ฆฌํ•ด ๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.๐Ÿ”ฅ ์งง์€ ์‹œ๊ฐ„์ด์ง€๋งŒ ์˜ฌ๋ผํ”„์—๊ฒŒ ์ •๋ง ๋งŽ์ด ๋ฐฐ์šธ ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ๋‹ค์Œ์— ๋‹ค๋ฅธ ๋ฏธ์…˜์œผ๋กœ ๋˜ ๋งŒ๋‚ฌ์œผ๋ฉด ์ข‹๊ฒ ์Šต๋‹ˆ๋‹ค. ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค.๐Ÿ™‡ # PR: STEP 3 ## ๊ณ ๋ฏผ๋˜์—ˆ๋˜ ์  ### ์ด๋ฏธ์ง€ ๋น„์œจ ๋ฌธ์ œ - ์ฒ˜์Œ์— ์ด๋ฏธ์ง€์— ์ œ์•ฝ์กฐ๊ฑด์„ ์ฃผ์—ˆ๊ณ , ๊ทธ์— ๋”ฐ๋ผ ์ด๋ฏธ์ง€์˜ ํฌ๊ธฐ๊ฐ€ ๋ณ€ํ•˜์˜€์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์ด๋ฏธ์ง€์˜ ํฌ๊ธฐ๊ฐ€ ๊ฐ•์ œ๋กœ ์žก์•„ ๋Š˜๋ฆฐ ๊ฒƒ์ฒ˜๋Ÿผ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. `contentMode`์˜ ๊ธฐ๋Šฅ์— ์ด๋ฏธ์ง€ ๋น„์œจ์— ๋”ฐ๋ผ ๋Š˜๋ ค์ฃผ๋Š” ๊ธฐ๋Šฅ์ธ `scaleAspectFit`์„ ์‚ฌ์šฉํ•ด์„œ ์ด๋ฏธ์ง€์˜ ํฌ๊ธฐ๋ฅผ ์กฐ์ ˆํ•  ๋•Œ ๋น„์œจ์— ๋งž์ถฐ ์กฐ์ •๋˜๋„๋ก ์„ค์ •ํ•˜์˜€์Šต๋‹ˆ๋‹ค. ### ์ปค์Šคํ…€ ์…€ ์‚ฌ์šฉ - `ItemUITableViewCellStyleSubtitle`์„ ์‚ฌ์šฉํ•˜์—ฌ `subtitle` ์Šคํƒ€์ผ์˜ ๊ธฐ๋ณธ ์…€์„ ์‚ฌ์šฉํ•˜์˜€์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์…€์˜ ์•„์ดํ…œ๋“ค์€ ๊ฐ๊ฐ ๋‹ค๋ฅธ ์ด๋ฏธ์ง€ ํฌ๊ธฐ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์…€์˜ `imageView`์— ์ œ์•ฝ์กฐ๊ฑด์„ ์ถ”๊ฐ€ํ•˜์—ฌ์•ผ ํ–ˆ์Šต๋‹ˆ๋‹ค. ๊ธฐ๋ณธ ์…€์˜ ๊ฒฝ์šฐ ๊ฐ๊ฐ์˜ ๋ทฐ์— ๋Œ€ํ•œ ์ œ์•ฝ์„ ์„ค์ •ํ•˜๊ธฐ๊ฐ€ ์–ด๋ ค์› ์Šต๋‹ˆ๋‹ค. ๊ฐ๊ฐ์˜ ๋ทฐ์— ๋Œ€ํ•ด ์ œ์•ฝ์„ ์ถ”๊ฐ€ํ•˜๊ธฐ ์œ„ํ•ด ์ปค์Šคํ…€ ์…€์„ ์‚ฌ์šฉํ•˜์—ฌ contentView์— ๋“ค์–ด๊ฐ€๋Š” ๋ทฐ๋“ค์˜ ์ œ์•ฝ์กฐ๊ฑด์„ ์ถ”๊ฐ€ํ•˜์˜€์Šต๋‹ˆ๋‹ค. - `itemImageView.heightAnchor`์˜ ๋†’์ด๋ฅผ ๊ธฐ์กด์—๋Š” ์…€์˜ `itemImageView.widthAnchor`์˜ ํฌ๊ธฐ์— ๋งž์ถ”์–ด ์ œ์•ฝ์กฐ๊ฑด์„ ์ถ”๊ฐ€ํ•˜์˜€์Šต๋‹ˆ๋‹ค. ```swift private func configureItemImageViewConstraint() { NSLayoutConstraint.activate([ itemImageView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 8), itemImageView.centerYAnchor.constraint(equalTo: contentView.centerYAnchor), itemImageView.widthAnchor.constraint(equalTo: contentView.widthAnchor, multiplier: 0.2), itemImageView.heightAnchor.constraint(equalTo: itemImageView.widthAnchor), itemImageView.topAnchor.constraint(greaterThanOrEqualTo: contentView.topAnchor, constant: 8), itemImageView.bottomAnchor.constraint(lessThanOrEqualTo: contentView.bottomAnchor, constant: -8) ]) } ``` ์ด๋Ÿฐ ๊ฒฝ์šฐ์— ํ™”๋ฉด์ด ํšŒ์ „ํ•˜๋ฉด `itemImageView`์˜ ๋†’์ด๊ฐ€ ์žฌ์„ค์ • ๋˜๋ฉฐ `topAnchor`์™€ `bottomAnchor`์˜ ์ œ์•ฝ์„ ํ†ตํ•ด ์ƒ๊ธด `imageItemView`์˜ ๋†’์ด์™€ ์ถฉ๋Œ์ด ๋ฐœ์ƒํ•˜์˜€์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•˜์—ฌ ์ œ์•ฝ์กฐ๊ฑด์— ์šฐ์„ ์ˆœ์œ„๋ฅผ ์„ ์ •ํ•˜์˜€์Šต๋‹ˆ๋‹ค. ```swift private func configureItemImageViewConstraint() { NSLayoutConstraint.activate([ itemImageView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 8), itemImageView.centerYAnchor.constraint(equalTo: contentView.centerYAnchor), itemImageView.widthAnchor.constraint(equalTo: contentView.widthAnchor, multiplier: 0.2), itemImageView.topAnchor.constraint(greaterThanOrEqualTo: contentView.topAnchor, constant: 8), itemImageView.bottomAnchor.constraint(lessThanOrEqualTo: contentView.bottomAnchor, constant: -8) ]) let itemImageViewHeightConstraint: NSLayoutConstraint = itemImageView.heightAnchor.constraint(equalTo: itemImageView.widthAnchor) itemImageViewHeightConstraint.priority = .defaultHigh itemImageViewHeightConstraint.isActive = true } ``` ### ํ™”๋ฉด์˜ ํšŒ์ „ ๊ณ ์ • - ์ฒซ ๋ฒˆ์งธ ํ™”๋ฉด์€ ์„ธ๋กœ๋กœ ๊ณ ์ •๋˜์–ด์•ผ ํ–ˆ๊ณ , ์•Œ์•„๋ณด๋˜ ์ค‘ `appDelegate`๋ฅผ ์ด์šฉํ•œ ๋ฐฉ๋ฒ•์„ ์‚ฌ์šฉํ•˜๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. `appDelegate`๋Š” ์•ฑ์˜ ๋ผ์ดํ”„ ์‚ฌ์ดํด๊ณผ ์ด๋ฒคํŠธ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฐ์ฒด๋กœ์จ ์•ฑ์˜ ์‹œ์ž‘, ์ข…๋ฃŒ ๋ฐ ์ƒํƒœ๋ณ€ํ™” ๋“ฑ๊ณผ ๊ด€๋ จ๋œ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค. - `appDelegate`์— ๋‹ค์Œ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์˜€์Šต๋‹ˆ๋‹ค. ```swift var changeOrientation: Bool = true func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask { if !changeOrientation { return [.all] } else { return [.portrait] } } ``` ๋งŒ์•ฝ `changeOrientation`๊ฐ€ `true`์ผ ๋•Œ ์ธํ„ฐํŽ˜์ด์Šค ๋ฐฉํ–ฅ์„ ๋‚˜ํƒ€๋‚ด๋Š”`UIInterfaceOrientationMask`๊ฐ€ ๋ชจ๋“  ๋ฐฉํ–ฅ์„ ํ—ˆ์šฉํ•˜๋„๋ก `[.all]`์„ ๋ฐ˜ํ™˜ํ•˜๊ณ , `false`์ธ ๊ฒฝ์šฐ ์„ธ๋กœ ๋ฐฉํ–ฅ๋งŒ์„ ํ—ˆ์šฉํ•˜๋„๋ก `[.portrait]`๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋„๋ก ํ–ˆ์Šต๋‹ˆ๋‹ค. `changeOrientation`์„ ํ™”๋ฉด ๊ณ ์ •์„ ์›ํ•˜๋Š” ์ฒซ ๋ฒˆ์งธ ๋ทฐ์—์„œ ๋ทฐ๊ฐ€ ๋‚˜ํƒ€๋‚  ๋•Œ `viewWillAppear`์—์„œ `true`๋กœ ์„ค์ •ํ•ด์„œ ์„ธ๋กœ๋กœ ๊ณ ์ •๋˜๊ฒŒ ํ•˜๊ณ , ๋‹ค๋ฅธ ๋ทฐ๋กœ ๋„˜์–ด๊ฐˆ ๋•Œ `viewWillDisappear`์—์„œ `false`๋กœ ์„ค์ •ํ•ด์„œ ํ™”๋ฉด์ด ๋ชจ๋“  ๋ฐฉํ–ฅ์„ ํ—ˆ์šฉํ•˜๋„๋ก ํ–ˆ์Šต๋‹ˆ๋‹ค. ### Dynamic type - Dynamic Type์€ ์‚ฌ์šฉ์ž๊ฐ€ ์›ํ•˜๋Š” ํ…์ŠคํŠธ ์‚ฌ์ด์ฆˆ๋กœ ์ปจํ…์ธ ๋ฅผ ๋ณผ ์ˆ˜ ์žˆ๋Š” ์œ ์—ฐ์„ฑ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. - ์ด ๋ถ€๋ถ„์— ๋Œ€ํ•ด์„œ ๊ธฐ์กด์— Dynamic Type์œผ๋กœ ํ…์ŠคํŠธ ํฌ๊ธฐ๋ฅผ ํ‚ค์šฐ๊ฒŒ ๋˜๋ฉด ...์œผ๋กœ ํ‘œ์‹œ๋˜๋Š” ๋ฌธ์ œ๋กœ ์ธํ•ด ์‚ฌ์šฉ์ž๊ฐ€ ๋‚ด์šฉ์„ ์•Œ ์ˆ˜ ์—†๋Š” ๋ฌธ์ œ๊ฐ€ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. `numberOfLines`์˜ ๊ฐ’์„ 0์œผ๋กœ ์„ค์ •ํ•ด์„œ ๋ชจ๋“  ํ…์ŠคํŠธ ๋ ˆ์ด๋ธ”์— ๋Œ€ํ•ด ํ…์ŠคํŠธ์˜ ํฌ๊ธฐ๊ฐ€ ์ปค์ ธ๋„ ๋‚ด์šฉ์„ ์•Œ ์ˆ˜ ์žˆ๋„๋ก `...`์ด ์•„๋‹Œ ์—ฌ๋Ÿฌ ์ค„๋กœ ํ‘œ์‹œํ•˜๋„๋ก ์„ค์ •ํ–ˆ์Šต๋‹ˆ๋‹ค. ## ํ•ด๊ฒฐ์ด ๋˜์ง€ ์•Š์€ ์  ### Layout ๋ฌธ์ œ - ๋‘ ๋ฒˆ์งธ ํ™”๋ฉด์ธ `ExpositionItemListViewController`์—์„œ debug view hierarchy๋ฅผ ํ™•์ธํ•˜๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. ``` Height and vertical position are ambiguous for UIImageView. ``` ์—๋Ÿฌ ๋ฉ”์‹œ์ง€๋ฅผ ํ™•์ธํ•˜๋ฉด `UIImageView`์˜ ๋†’์ด์™€ y์ถ•์˜ ์ขŒํ‘œ๊ฐ€ ๋ถˆ๋ถ„๋ช…ํ•˜๋‹ค๋Š” ๋‚ด์šฉ์œผ๋กœ ์ดํ•ดํ–ˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์ œ์•ฝ ์กฐ๊ฑด์„ ์ถ”๊ฐ€ํ•˜๋Š” ๋ถ€๋ถ„์„ ๋ณด๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค. ```swift private func configureItemImageViewConstraint() { NSLayoutConstraint.activate([ ... itemImageView.centerYAnchor.constraint(equalTo: contentView.centerYAnchor), ... ]) let itemImageViewHeightConstraint: NSLayoutConstraint = itemImageView.heightAnchor.constraint(equalTo: itemImageView.widthAnchor) itemImageViewHeightConstraint.priority = .defaultHigh itemImageViewHeightConstraint.isActive = true } ``` ์ด์ฒ˜๋Ÿผ ์ง์ ‘ `heightAnchor`๋ฅผ ํ†ตํ•ด `itemImageView`์˜ ๋†’์ด๋ฅผ `centerYAnchor`๋ฅผ ํ†ตํ•ด y์ถ•์— ๋Œ€ํ•œ ์ œ์•ฝ์กฐ๊ฑด์„ ์ถ”๊ฐ€ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์œ„์˜ ์—๋Ÿฌ๋Š” ์–ด๋–ป๊ฒŒ ํ•ด๊ฒฐํ•ด์•ผ ํ• ๊นŒ์š”? ### ImageView์— ์ œ์•ฝ์กฐ๊ฑด ์ถ”๊ฐ€์‹œ dynamicType ์ ์šฉ - ๊ฐœ๋ณ„์ ์ธ ์ด๋ฏธ์ง€ ํŒŒ์ผ์˜ ํฌ๊ธฐ๋ฅผ `ImageView`์—์„œ ํ†ต์ผ๋œ ํฌ๊ธฐ๋กœ ๋ณด์—ฌ์ฃผ๊ธฐ ์œ„ํ•˜์—ฌ `ImageView`์— ์ œ์•ฝ์กฐ๊ฑด์„ ์ถ”๊ฐ€ํ•˜์˜€์Šต๋‹ˆ๋‹ค. `ImageView`์— ํ…์ŠคํŠธ์™€ ๊ฐ™์ด ๋‹ค์ด๋‚˜๋ฏนํƒ€์ž…์„ ์ ์šฉํ•ด์„œ ๋Š˜์–ด๋‚˜๊ฑฐ๋‚˜ ์ค„์–ด๋“ค๊ฒŒ ํ•˜๊ณ  ์‹ถ์—ˆ์œผ๋‚˜, `ImageView`์˜ ์ œ์•ฝ์กฐ๊ฑด ๋•Œ๋ฌธ์— ๋‹ค์ด๋‚˜๋ฏนํƒ€์ž… ์ ์šฉ์ด ๋ถˆ๊ฐ€๋Šฅํ•œ ๋ฌธ์ œ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Ÿฐ ๊ฒฝ์šฐ์—๋Š” ์–ด๋–ป๊ฒŒ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์„๊นŒ์š”?