# ์ผ๊ธฐ์žฅ ๐Ÿ“” 2023-08-28 ~ 2023-09-15 ## Ground Rules ### ๊ทœ์น™ - ์›”/๋ชฉ - ์˜ค์ „์€ ๊ฐ์ž ํ•™์Šตํ™œ๋™ ์˜ˆ์Šต - ํ•™์Šตํ™œ๋™ 14:00 ~ 17:00 - ์ €๋…์‹์‚ฌ 17:00 ~ 18:00 - ์Šคํฌ๋Ÿผ 18:00 - ํŒ€ํ”„๋กœ์ ํŠธ 18:05 ~ 20:00 - ํ™”/์ˆ˜/๊ธˆ - ์Šคํฌ๋Ÿผ ์ดํ›„ ํ”„๋กœ์ ํŠธ ์ง„ํ–‰ - ์Šคํฌ๋Ÿผ 10:00 - ํŒ€ํ”„๋กœ์ ํŠธ 10:05 ~ 12:00 - ์ ์‹ฌ์‹์‚ฌ 12:00 ~ 13:00 - ํŒ€ํ”„๋กœ์ ํŠธ 13:00 ~ 18:00 - ์ฃผ๋ง์€ ํŠน๋ณ„ํ•œ ์ผ ์—†์œผ๋ฉด ์ž์œ ์‹œ๊ฐ„ ### ์ผ์ •์ƒ ํŠน์ด์‚ฌํ•ญ - Max - 9/1(๊ธˆ) ์˜ค์ „ 11์‹œ๋ถ€ํ„ฐ ๊ฐ€๋Šฅ! (๋ฌธ์ œ์ถœ์ œ ๊ด€๋ จ์œผ๋กœ ํ•œ์‹œ๊ฐ„๋งŒ ๋น„์šฐ๊ฒ ์Šต๋‹ˆ๋‹คใ… ) - 9/8(๊ธˆ) ๊ธˆ์š”์ผ ์˜คํ›„์— ํ”ผ์น˜๋ชปํ•  ์‚ฌ์ •์ด ์žˆ์Šต๋‹ˆ๋‹คใ… ใ…  ์•„์นจ ๋ฆฌ๋“œ๋ฏธ ์ •๋ฆฌ๋Š” ๊ฐ€๋Šฅํ• ๊ฑฐ๊ฐ™์• ์šฉ - 9/14(๋ชฉ) ์ €๋…์— ์•ฝ์†์ด ์žˆ์Šต๋‹ˆ๋‹ค!(ํ•™์Šตํ™œ๋™ ๋๋‚˜๊ณ  ๋‹ค์‹œ ๋ชจ์ด๊ธฐ ์–ด๋ ค์šธ ๊ฒƒ ๊ฐ™์•„์š”ใ… ) - Hemg - 9.4์ผ ์›”์š”์ผ ์ €๋…์—๋Š” ์•ฝ์†์ด ์žˆ์Šต๋‹ˆ๋‹ค. ### ์Šคํฌ๋Ÿผ(5๋ถ„ ๋‚ด์™ธ) - ์›”/๋ชฉ 18:00 - ํ™”/์ˆ˜/๊ธˆ 10:00 ### ํ”„๋กœ์ ํŠธ ๊ทœ์น™ - ๋„ค์ด๋ฐ ์ค€์ˆ˜ํ•˜๊ธฐ(๊ฐ€์ด๋“œ ๋ผ์ธ) - ๋ธŒ๋žœ์น˜ - ์Šคํ…๋ณ„๋กœ - ์ปค๋ฐ‹ ๋ฉ”์‹œ์ง€ - ์นด๋ฅด๋งˆ์Šคํƒ€์ผ - ๋ฆฌ๋“œ๋ฏธ์ž‘์„ฑ๊ณผ PR์ •๋ฆฌ๋ฅผ ์œ„ํ•ด ์ฝ”๋“œ์— ๋Œ€ํ•œ ๊ธฐ๋ก ๊ทธ๋•Œ ๊ทธ๋•Œ ํ•˜๊ธฐ - ์ค‘๊ฐ„์ค‘๊ฐ„ ๋ฆฌ๋“œ๋ฏธ ์ž‘์„ฑ ## ์ผ์ผ ์Šคํฌ๋Ÿผ ### ๐Ÿ™Œ 08/28(์›”) - ์˜ค๋Š˜์˜ ์ปจ๋””์…˜ - Max๐Ÿค–: ์‰ฌ๊ณ  ์™”๋”๋‹ˆ ๋จธ๋ฆฌ๊ฐ€ ๋ฉํ•˜๋„ค์š”... ๊ทธ์น˜๋งŒ ํž˜๋‚ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹นใ…Žใ…Žใ…Ž - Hemg๐Ÿป: ์•„์ฃผ ์ข‹์Šต๋‹ˆ๋‹ค. ๋น„๊ฐ€ ์™€์„œ ํ‘น์ž๊ณ  ์ผ์–ด๋‚ฌ๋„ค์š” - ํŠน์ด์‚ฌํ•ญ - Max๐Ÿค–: ์—†์Šต๋‹ˆ๋‹ค! - Hemg๐Ÿป: ์˜ค๋Š˜์€ ๋”ฑํžˆ ์—†์Šต๋‹ˆ๋‹ค! - ์˜ค๋Š˜ ํ•  ์ผ - [x] ํ™œ๋™ํ•™์Šต ์˜ˆ์Šต - [x] STEP 1 ํŒŒ์•… ### ๐Ÿ™Œ 08/29(ํ™”) - ์˜ค๋Š˜์˜ ์ปจ๋””์…˜ - Max๐Ÿค–: ์ปจ๋””์…˜ ๋‚˜์˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค! ๊ทผ๋ฐ ๋น„๊ฐ€ ์™€์„œ ์ข€ ๋Š˜์–ด์ง€๋„ค์š”ใ…  - Hemg๐Ÿป: ํ‰๋ฒ”ํ•œ๊ฑฐ ๊ฐ™์Šต๋‹ˆ๋‹ค ใ…‹ - ํŠน์ด์‚ฌํ•ญ - Max๐Ÿค–: ์—†์Šต๋‹ˆ๋‹น - Hemg๐Ÿป: ๋ฐฉํ•™ ์ดํ›„ ์ฃผ๋ผ์„œ ๊ทธ๋Ÿฐ๊ฐ€ ์ •๋ง ์ง‘์ค‘์ด ์ž˜์•ˆ๋˜๊ธดํ•˜๋„ค์š”ใ…  - ์˜ค๋Š˜ ํ•  ์ผ - [x] STEP1 ์ž‘์—… - [x] STEP1์— ํ•„์š”ํ•œ ๋ฌธ์„œ ํ™•์ธ ### ๐Ÿ™Œ 08/30(์ˆ˜) - ์˜ค๋Š˜์˜ ์ปจ๋””์…˜ - Max๐Ÿค–: ์ปจ๋””์…˜ ๊ดœ์ฐฎ์Šต๋‹ˆ๋‹ค - Hemg๐Ÿป: ์–ด์ œ ์ปคํ”ผ๋ฅผ ๋Šฆ๊ฒŒ ๋งˆ์…”์„œ ๊ทธ๋Ÿฐ๊ฐ€ ์ž ์„ ์ข€ ๋ชป์žค์Šต๋‹ˆ๋‹ค ใ…‹ - ํŠน์ด์‚ฌํ•ญ - Max๐Ÿค–: ์—†์Šต๋‹ˆ๋‹ค! - Hemg๐Ÿป: ๋”ฐ๋กœ ์—†์Šต๋‹ˆ๋‹ค! - ์˜ค๋Š˜ ํ•  ์ผ - [x] STEP1 PR ์ง„ํ–‰ - [ ] (๋ฆฌ๋ทฐ ์˜ค๋ฉด) STEP1 ์ˆ˜์ • ์ง„ํ–‰ - [ ] CoreData ๊ณต๋ถ€ ### ๐Ÿ™Œ 08/31(๋ชฉ) - ์˜ค๋Š˜์˜ ์ปจ๋””์…˜ - Max๐Ÿค–: ์ปจ๋””์…˜ ๊ดœ์ฐฎ์Šต๋‹ˆ๋‹น! - Hemg๐Ÿป: ํ‘น์ž๊ณ  ์ผ์–ด๋‚˜์„œ ์ข‹์Šต๋‹ˆ๋‹ค. - ํŠน์ด์‚ฌํ•ญ - Max๐Ÿค–: ์—†์Šต๋‹ˆ๋‹ค - Hemg๐Ÿป: ์—†์Šต๋‹ˆ๋‹ค - ์˜ค๋Š˜ ํ•  ์ผ - [x] ํ™œ๋™ํ•™์Šต ์˜ˆ์Šต - [ ] CoreData ๊ณต๋ถ€ ### ๐Ÿ™Œ 09/01(๊ธˆ) - ์˜ค๋Š˜์˜ ์ปจ๋””์…˜ - Max๐Ÿค–: ์ปจ๋””์…˜์€ ์ข‹์Šต๋‹ˆ๋‹ค ๋‚ ์”จ๋„ ์ข‹๋„ค์šฉ - Hemg๐Ÿป: ํ—ค๋กฑํ—ค๋กฑ ํ•ฉ๋‹ˆ๋‹ค - ํŠน์ด์‚ฌํ•ญ - Max๐Ÿค–: ์—†์Šต๋‹ˆ๋‹ค~ - Hemg๐Ÿป: ์—†์Šต๋‹ˆ๋‹ค. coreData Manual/None // class definition - ์˜ค๋Š˜ ํ•  ์ผ - [x] STEP 1 ์ถ”๊ฐ€ ์ˆ˜์ •์‚ฌํ•ญ ์ง„ํ–‰ - [x] STEP 2 : CoreData ์—ฐ๊ฒฐ ### ๐Ÿ™Œ 09/02(์›”) - ์˜ค๋Š˜์˜ ์ปจ๋””์…˜ - Max๐Ÿค–: ์ปจ๋””์…˜ ์ข‹์Šต๋‹ˆ๋‹น - Hemg๐Ÿป: ์ข‹์Šต๋‹ˆ๋‹ค! - ํŠน์ด์‚ฌํ•ญ - Max๐Ÿค–: ์—†์Šต๋‹ˆ๋‹ค - Hemg๐Ÿป: ์—†์Šต๋‹ˆ๋‹ค. - ์˜ค๋Š˜ ํ•  ์ผ - [ ] STEP 2 ์ง„ํ–‰ - [ ] . ### ๐Ÿ™Œ 09/03(ํ™”) - ์˜ค๋Š˜์˜ ์ปจ๋””์…˜ - Max๐Ÿค–: ๊ดœ์ฐฎ์Šต๋‹ˆ๋‹น - Hemg๐Ÿป: ์ข‹์Šต๋‹ˆ๋‹ค. - ํŠน์ด์‚ฌํ•ญ - Max๐Ÿค–: ์—†์Šต๋‹ˆ๋‹ค! - Hemg๐Ÿป: ์—†์Šต๋‹ˆ๋‹ค. - ์˜ค๋Š˜ ํ•  ์ผ - [x] STEP 2 ์ง„ํ–‰ ### ๐Ÿ™Œ 09/04(์ˆ˜) - ์˜ค๋Š˜์˜ ์ปจ๋””์…˜ - Max๐Ÿค–: ์‹ ๋‚ฉ๋‹ˆ๋‹น - Hemg๐Ÿป: ๋„ˆ๋ฌด ์ข‹์Šต๋‹ˆ๋‹ค. - ํŠน์ด์‚ฌํ•ญ - Max๐Ÿค–: ๋ชจ๊ฐ์ฝ” - Hemg๐Ÿป: ํ•ฉ์ •์œผ๋กœ ๊ฐ€์š”. ~~์ง€๊ฐ..~~ - ์˜ค๋Š˜ ํ•  ์ผ - [x] STEP 2 PR ์ง„ํ–‰ - [x] README ์ž‘์„ฑ --- # PR ## STEP 1 > ์ผ๊ธฐ์žฅ [STEP 1] Hamg, maxhyunm ์•ˆ๋…•ํ•˜์„ธ์š” ๊ทธ๋ฆฐ!(@GREENOVER) ์ด๋ฒˆ ๋ฆฌ๋ทฐ๋ฅผ ๋ฐ›๊ฒŒ ๋œ Max, Hemg ์ž…๋‹ˆ๋‹ค. 3์ฃผ๊ฐ„ ์ž˜ ๋ถ€ํƒ๋“œ๋ฆฝ๋‹ˆ๋‹ค. ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค!๐Ÿ™‡โ€โ™‚๏ธ ์ผ๊ธฐ์žฅ ์ฒซ PR ์ง„ํ–‰ํ–ˆ์Šต๋‹ˆ๋‹ค. ์‹œ๊ฐ„ ๋˜์‹ค๋•Œ ํ™•์ธ ๋ถ€ํƒ๋“œ๋ฆฝ๋‹ˆ๋‹ค! ## ๊ณ ๋ฏผํ–ˆ๋˜ ์  ### ๐Ÿ”น ํ‚ค๋ณด๋“œ ๋†’์ด์— ๋”ฐ๋ฅธ ํ…์ŠคํŠธ๋ทฐ ํฌ๊ธฐ ๋ณ€๊ฒฝ ํ‚ค๋ณด๋“œ๊ฐ€ ํ™”๋ฉด์— ๋‚˜ํƒ€๋‚˜๊ณ  ์‚ฌ๋ผ์ง์— ๋”ฐ๋ผ ํ…์ŠคํŠธ๋ทฐ ์˜์—ญ์˜ ํฌ๊ธฐ๊ฐ€ ํ•จ๊ป˜ ๋ฐ”๋€Œ๋„๋ก ์ž‘์—…ํ•ด์•ผ ํ–ˆ๋Š”๋ฐ, ์•„๋ž˜ ๋‘ ๊ฐ€์ง€ ๋ฐฉ๋ฒ• ์ค‘์—์„œ ๊ณ ๋ฏผ์ด ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. - `NotificationCenter`๋ฅผ ํ™œ์šฉํ•˜์—ฌ ํ‚ค๋ณด๋“œ๊ฐ€ ํ™œ์„ฑํ™”๋  ๋•Œ๋งˆ๋‹ค ์•Œ๋ฆผ์„ ๋ฐ›์•„ `TextView`์˜ `contentInset`์„ ๋ณ€๊ฒฝํ•˜๋Š” ๋ฐฉ๋ฒ• - `TextView`์˜ `bottomAnchor`๋ฅผ `keyboardLayoutGuide`์˜ `topAnchor`์™€ ๋งž์ถ”๋Š” ๋ฐฉ๋ฒ• - ์˜ˆ์‹œ) ```swift textView.bottomAnchor.constraint(equalTo: view.keyboardLayoutGuide.topAnchor) ``` ๋‘ ๋ฒˆ์งธ ๋ฐฉ๋ฒ•์€ ์ฝ”๋“œ ํ•œ ์ค„๋งŒ ์ถ”๊ฐ€๋˜๋ฉด ๋˜๊ธฐ ๋•Œ๋ฌธ์— ํ›จ์”ฌ ์‹ฌํ”Œํ•˜์ง€๋งŒ `iOS 15` ๋ฒ„์ „ ์ด์ƒ์—์„œ๋งŒ ํ™œ์šฉ์ด ๊ฐ€๋Šฅํ•˜์—ฌ, ํ•ด๋‹น ๋ถ€๋ถ„์€ ์ผ๋‹จ ์ „์ž๋กœ ์ง„ํ–‰ํ•˜์˜€์Šต๋‹ˆ๋‹ค. ### ๐Ÿ”น ํ…Œ์ด๋ธ” ๋ทฐ ์…€ contentView ํ…Œ์ด๋ธ”๋ทฐ ์…€์—์„œ `label` ๋‚ด์šฉ๋“ค์„ `cell` ์ž์ฒด์— `addSubview`๋กœ ์ถ”๊ฐ€ํ• ์ง€, ์•„๋‹ˆ๋ฉด `contentView`์— ์ถ”๊ฐ€ํ• ์ง€ ๊ณ ๋ฏผํ•˜์˜€์Šต๋‹ˆ๋‹ค. `cell` ์ž์ฒด์— `addSubview`๋ฅผ ํ•˜๋ฉด `safeAreaLayoutGuide`๋ฅผ ๊ธฐ์ค€์œผ๋กœ ๋ ˆ์ด์•„์›ƒ์„ ์žก์„ ์ˆ˜ ์žˆ๊ณ , `contentView`์— ์ถ”๊ฐ€ํ•˜๋ฉด `contentView`๋ฅผ ๊ธฐ์ค€์œผ๋กœ ๋ ˆ์ด์•„์›ƒ์„ ์žก๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ์ €ํฌ๋Š” `safeArea`๋ฅผ ํ™œ์šฉํ•˜๊ณ  ์‹ถ์–ด์„œ ์ฒ˜์Œ์—๋Š” `cell`์— ์ง์ ‘ ์ถ”๊ฐ€ํ•˜๋Š” ๋ฐฉ์‹์„ ๊ณ ๋ คํ–ˆ์ง€๋งŒ `contentView`์˜ ๊ณต์‹๋ฌธ์„œ๋ฅผ ํ™•์ธํ•ด ๋ณด๋‹ˆ ์•„๋ž˜์™€ ๊ฐ™์ด ์ •๋ฆฌ๋˜์–ด ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. > ์…€์˜ ์‚ฌ์šฉ์ž ์ง€์ • ์ปจํ…์ธ ๋ฅผ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ๋Š” ๋ฉ”์ธ ๋ทฐ. > ์…€์„ ๊ตฌ์„ฑํ•  ๋•Œ, ์…€์— ๋“ค์–ด๊ฐ€์•ผ ํ•˜๋Š” ์ปจํ…์ธ ๋ฅผ ์ด ๋ทฐ์— ์ถ”๊ฐ€ํ•˜๋ฉด ์…€์€ ์ž๋™์œผ๋กœ ๋ฐฐ๊ฒฝ์— ๋“ค์–ด๊ฐ€๋Š” ๋‹ค๋ฅธ ๋ทฐ๋“ค๋ณด๋‹ค ์•ž์ชฝ์— ์ด ์ปจํ…์ธ ๋“ค์„ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค. `label`๋“ค์€ ๊ฐ€์žฅ ์•ž์ชฝ์— ๋ณด์—ฌ์ ธ์•ผ ํ•˜๋Š” ๋‚ด์šฉ์ด๋ฏ€๋กœ, `contentView`์— ์ถ”๊ฐ€ํ•˜๋Š” ์ชฝ์œผ๋กœ ์ˆ˜์ •ํ•˜์—ฌ ์ง„ํ–‰ํ•˜์˜€์Šต๋‹ˆ๋‹ค. ```swift contentView.addSubview(titleLabel) titleLabel.topAnchor.constraint(equalTo: contentView.topAnchor) ``` ### ๐Ÿ”น ๋ฐ˜๋ณต์ ์ธ ๋‚ ์งœ ํฌ๋งคํŒ… ์ฒ˜๋ฆฌ ์š”๊ตฌ์‚ฌํ•ญ์„ ํ™•์ธํ–ˆ์„ ๋•Œ, ์ผ๊ธฐ ๋ฆฌ์ŠคํŠธ ํ™”๋ฉด๊ณผ ์ƒˆ๋กœ์šด ์ผ๊ธฐ๋ฅผ ์ƒ์„ฑํ•˜๋Š” ํ™”๋ฉด์—์„œ ๋ชจ๋‘ ์•„๋ž˜์™€ ๊ฐ™์ด ๋‚ ์งœ ํฌ๋งคํŒ…์„ ์‚ฌ์šฉํ•ด์•ผ ํ•˜๋Š” ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ```swift private let formatter: DateFormatter = { let formatter = DateFormatter() formatter.locale = Locale(identifier: "ko_kr") formatter.dateFormat = "yyyy๋…„MM์›”dd์ผ" return formatter }() ``` ๋™์ผํ•œ ์ฝ”๋“œ๊ฐ€ ๋‘ ๊ฐœ์˜ `ViewController`์—์„œ ๋ฐ˜๋ณต๋˜๋Š” ๊ฒƒ์„ ๋ง‰๊ธฐ ์œ„ํ•ด ํ•ด๋‹น ์ฝ”๋“œ๋ฅผ ๋ถ„๋ฆฌํ•˜๊ณ ์ž ํ–ˆ์Šต๋‹ˆ๋‹ค. ์ด๋•Œ ๋‹จ์ˆœํžˆ ๋˜‘๊ฐ™์€ ํ˜•ํƒœ์˜ ํฌ๋งคํ„ฐ๋ฅผ ์‹ฑ๊ธ€ํ†ค ๋ฐฉ์‹์œผ๋กœ ๋งŒ๋“ค์ง€, ์•„๋‹ˆ๋ฉด ๋‹ค์–‘ํ•œ ํฌ๋งท์œผ๋กœ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก `extension`์œผ๋กœ ๋ฉ”์„œ๋“œ๋ฅผ ๋งŒ๋“ค์ง€ ๊ณ ๋ฏผํ•˜์˜€์ง€๋งŒ ํ™•์žฅ์„ฑ์„ ๊ณ ๋ คํ•˜์—ฌ ํ›„์ž๋ฅผ ์„ ํƒํ•˜์˜€์Šต๋‹ˆ๋‹ค. ```swift extension DateFormatter { func formatToString(from date: Date, with format: String) -> String { self.dateFormat = format return self.string(from: date) } } DateFormatter().formatToString(from: entity.createdAt, with: "YYYY๋…„ MM์›” dd์ผ") ``` ## ์กฐ์–ธ์ด ํ•„์š”ํ•œ ์  ### ํ‚ค๋ณด๋“œ ํ™œ์šฉ์‹œ ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ ์—๋Ÿฌ ๋ฐœ์ƒ - command + k ๋กœ ์ง„ํ–‰ํ•˜์—ฌ ํ‚ค๋ณด๋“œ๋ฅผ ๋ถˆ๋Ÿฌ ์™”์„๋•Œ ์•„๋ž˜์™€ ๊ฐ™์€ ๋ฌธ๊ตฌ๊ฐ€ ๋‚˜ํƒ€๋‚˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ ์—๋Ÿฌ๋กœ ํ™•์ธ๋˜๋Š”๋ฐ, ํ˜น์‹œ ์ด๋ถ€๋ถ„์— ๋Œ€ํ•ด์„œ๋Š” ๋”ฐ๋กœ ๋” ํ•ด๊ฒฐ ํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์ด ์žˆ์„๊นŒ์š”? ``` Diary[88951:12721534] [HardwareKeyboard] -[UIApplication getKeyboardDevicePropertiesForSenderID:shouldUpdate:usingSyntheticEvent:], failed to fetch device property for senderID (778835616971358209) use primary keyboard info instead. ``` --- # PR ## STEP 2 > ์ผ๊ธฐ์žฅ [STEP 2] Hamg, maxhyunm ์•ˆ๋…•ํ•˜์„ธ์š” ๊ทธ๋ฆฐ!(@GREENOVER) ์ผ๊ธฐ์žฅ STEP 2 PR ์˜ฌ๋ ค๋“œ๋ฆฝ๋‹ˆ๋‹ค. ์ฝ”์–ด ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ๋กœ ๋งŽ์ด ํ—ค๋งธ๋˜ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค๐Ÿ˜‚ ์ด๋ฒˆ ์Šคํ…๋„ ๋ฆฌ๋ทฐ ์ž˜ ๋ถ€ํƒ๋“œ๋ฆฝ๋‹ˆ๋‹ค!๐Ÿ™‡โ€ ## ๊ณ ๋ฏผํ–ˆ๋˜ ์  ### ๐Ÿ”น Core Data ์ฒ˜๋ฆฌ์šฉ ๋กœ์ง ์œ„์น˜ ์ฒ˜์Œ์—๋Š” `SceneDelegate` ๋˜๋Š” `AppDelegate`์— `PersistentContainer`์™€ `saveContext` ๋ฉ”์„œ๋“œ๋ฅผ ๋‘๋ ค๊ณ  ํ–ˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์ด ๋‘ ๊ฐ€์ง€ ๋‚ด์šฉ์„ ํฌํ•จํ•˜์—ฌ, CRUD ์ž‘์—… ์ „๋ฐ˜๊ณผ ๊ด€๋ จ๋œ ๋‚ด์šฉ์„ ๋ณ„๊ฐœ์˜ ํƒ€์ž…์œผ๋กœ ๋ถ„๋ฆฌํ•˜์—ฌ `CoreDataManager`๋ฅผ ๋งŒ๋“ค๋ฉด ์ข€ ๋” ๊น”๋”ํžˆ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์„ ๊ฒƒ ๊ฐ™๋‹ค๋Š” ์ƒ๊ฐ์ด ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค. ์ด์— ๋”ฐ๋ผ ์•„๋ž˜์™€ ๊ฐ™์ด ์ƒˆ๋กœ์šด ํƒ€์ž…์„ ์ƒ์„ฑํ•˜์˜€์Šต๋‹ˆ๋‹ค. Manager ํƒ€์ž…์€ ํ•˜๋‚˜์˜ ์ธ์Šคํ„ด์Šค๋ฅผ ์•ฑ ์ „์ฒด๊ฐ€ ๊ณตํ†ต์ ์œผ๋กœ ์‚ฌ์šฉํ•˜๋ฏ€๋กœ ์‹ฑ๊ธ€ํ†ค ์ฒ˜๋ฆฌ๋ฅผ ํ•˜์˜€์Šต๋‹ˆ๋‹ค. ```swift class CoreDataManager { static let shared = CoreDataManager() private init() {} func fetchDiary() throws -> [Diary] { ... } func createDiary() -> Diary { ... } func deleteDiary(_ diary: Diary?) { ... } // MARK: - Core Data stack lazy var persistentContainer: NSPersistentContainer = { ... }() func saveContext () { ... } } ``` ### ๐Ÿ”น ์ผ๊ธฐ ์ƒ์„ฑ/์ˆ˜์ • ์ฒ˜๋ฆฌ ๋ฐฉ์‹ ์ผ๊ธฐ ์‹ ๊ทœ ์ƒ์„ฑ๊ณผ ๊ธฐ์กด ์ผ๊ธฐ ์ˆ˜์ • ์ž‘์—…์€ ๋ชจ๋‘ `CreateDiaryViewController`๋ฅผ ํ†ตํ•ด ๊ตฌํ˜„๋ฉ๋‹ˆ๋‹ค. ์ด๋•Œ ์‹ ๊ทœ/์ˆ˜์ • ์—ฌ๋ถ€๋ฅผ ๊ตฌ๋ถ„ํ•˜๊ธฐ ์œ„ํ•ด, ์ˆ˜์ • ์ž‘์—…์ธ ๊ฒฝ์šฐ์—๋Š” `init`์„ ํ†ตํ•ด ์ˆ˜์ •ํ•  ์ผ๊ธฐ ์ธ์Šคํ„ด์Šค๋ฅผ ์ „๋‹ฌํ•ด์ค„ ์ˆ˜ ์žˆ๋„๋ก ์ƒˆ๋กœ์šด ์ƒ์„ฑ์ž๋ฅผ ์ถ”๊ฐ€ํ•˜์˜€์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ ์‹ ๊ทœ ์ž‘์—…์ธ ๊ฒฝ์šฐ์—๋Š” `init`์—์„œ uuid, ๋‚ ์งœ๋งŒ ์ž…๋ ฅ๋œ ์ƒˆ๋กœ์šด `diary` ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•˜๋„๋ก ํ•˜์˜€์Šต๋‹ˆ๋‹ค. ```swift init() { self.diary = CoreDataManager.shared.createDiary() self.isNew = true super.init(nibName: nil, bundle: nil) } init(_ diary: Diary) { self.diary = diary self.isNew = false super.init(nibName: nil, bundle: nil) } ``` ### ๐Ÿ”น ์ผ๊ธฐ ์ž๋™์ €์žฅ ๊ตฌํ˜„ ๋ฐฉ์‹ ์š”๊ตฌ์‚ฌํ•ญ์— ๋”ฐ๋ฅธ ์ž๋™์ €์žฅ ์‹œ๊ธฐ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์•˜์Šต๋‹ˆ๋‹ค. - ์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅ์„ ๋ฉˆ์ถ”๋Š” ๊ฒฝ์šฐ(ํ‚ค๋ณด๋“œ๊ฐ€ ์‚ฌ๋ผ์ง€๋Š” ๊ฒฝ์šฐ) - ์•ฑ์ด ๋ฐฑ๊ทธ๋ผ์šด๋“œ๋กœ ์ง„์ž…ํ•˜๋Š” ๊ฒฝ์šฐ - ์ด์ „ ํ™”๋ฉด(๋ฆฌ์ŠคํŠธ ํ™”๋ฉด)์œผ๋กœ ์ด๋™ํ•˜๋Š” ๊ฒฝ์šฐ ์ด์— ๋”ฐ๋ผ `UITextViewDelegate`์˜ `textViewDidEndEditing` ๋ฉ”์„œ๋“œ๋ฅผ ํ™œ์šฉํ•˜์—ฌ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๊ตฌํ˜„ํ•˜์˜€์Šต๋‹ˆ๋‹ค. ```swift func textViewDidEndEditing(_ textView: UITextView) { let contents = textView.text.split(separator: "\n") guard !contents.isEmpty else { return } CoreDataManager.shared.saveContext() } ``` ์œ„์˜ ์ž‘์—…์„ ํ†ตํ•ด ํ‚ค๋ณด๋“œ ๋น„ํ™œ์„ฑํ™”์™€ ํ™”๋ฉด ๋’ค๋กœ๊ฐ€๊ธฐ ์ž‘์—…์‹œ์—๋Š” ์ž๋™์œผ๋กœ ๋‚ด์šฉ์„ title / body ๋กœ ๋ถ„๋ฆฌํ•˜์—ฌ ์ €์žฅํ•  ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์•ฑ์ด ๋ฐฑ๊ทธ๋ผ์šด๋“œ์— ์ง„์ž…ํ•˜๋Š” ๊ฒฝ์šฐ๋Š” `SceneDelegate`์˜ `sceneDidEnterBackground` ๋ฉ”์„œ๋“œ๋ฅผ ํ†ตํ•ด ์ž‘์—…ํ•˜๋Š” ๊ฒƒ์œผ๋กœ ์•Œ๊ณ  ์žˆ์–ด, `CreateDiaryViewController` ๋‚ด๋ถ€์˜ textView ๋‚ด์šฉ์— ์ ‘๊ทผํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์ฐพ๊ธฐ๊ฐ€ ์–ด๋ ค์› ์Šต๋‹ˆ๋‹ค. ์‹ฑ๊ธ€ํ†ค์œผ๋กœ ๋งŒ๋“ค์–ด์ง„ `CoreDataManager`์˜ `saveContext`๋ฉ”์„œ๋“œ๋ฅผ ํ†ตํ•ด ํ•ด๋‹น ์ผ๊ธฐ ๊ฐ์ฒด๋ฅผ ์ฝ”์–ด๋ฐ์ดํ„ฐ์— ์ €์žฅํ•  ์ˆ˜๋Š” ์žˆ์—ˆ์ง€๋งŒ, ํ˜„์žฌ ์ž‘์„ฑ๋œ ์ผ๊ธฐ ๋‚ด์šฉ์„ title๊ณผ body๋กœ ๋‚˜๋ˆ„์–ด ์—…๋ฐ์ดํŠธํ•˜๋Š” ์ž‘์—…์ด ๋ถˆ๊ฐ€๋Šฅํ–ˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ์ฒ˜์Œ์—๋Š” `CreateViewController`์˜ `viewWillDisappear` ๋ฉ”์„œ๋“œ์—์„œ ์ €์žฅ์ฒ˜๋ฆฌ๋ฅผ ์ง„ํ–‰ํ•  ์ˆ˜ ์žˆ๋„๋ก ๊ตฌํ˜„ํ•ด ๋ณด์•˜์Šต๋‹ˆ๋‹ค. ```swift override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) saveDiary() } ``` ํ•˜์ง€๋งŒ ์ด๋ ‡๊ฒŒ ํ•˜๋‹ˆ ์ผ๊ธฐ ์‚ญ์ œ ์ฒ˜๋ฆฌ๋ฅผ ํ•œ ๋’ค ๋ทฐ์ปจํŠธ๋กค๋Ÿฌ๋ฅผ popํ•  ๋•Œ์—๋„ ์ €์žฅ์ฒ˜๋ฆฌ๋ฅผ ๊ฑฐ์น˜๊ฒŒ ๋˜์–ด, ์˜ค๋ฅ˜๋ฅผ ํ”ผํ•˜๋ ค๋‹ค ๋ณด๋‹ˆ ์˜คํžˆ๋ ค ๋กœ์ง์ด ๋ณต์žกํ•ด์กŒ์Šต๋‹ˆ๋‹ค. ๋•Œ๋ฌธ์— ์ €ํฌ๋Š” `TextView`๊ฐ€ ์ˆ˜์ •๋  ๋•Œ๋งˆ๋‹ค ๋ทฐ์ปจํŠธ๋กค๋Ÿฌ๊ฐ€ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” ์ผ๊ธฐ ๊ฐ์ฒด์˜ ๋‚ด์šฉ์„ ๋ฐ”๊ฟ”์ฃผ๊ณ , ์ €์žฅ์ด ํ•„์š”ํ•œ ์ˆœ๊ฐ„์— `saveContext` ์ฒ˜๋ฆฌ๋งŒ ์ง„ํ–‰ํ•  ์ˆ˜ ์žˆ๋„๋ก ์•„๋ž˜์™€ ๊ฐ™์ด ๊ตฌํ˜„ํ•˜์˜€์Šต๋‹ˆ๋‹ค. ```swift func textViewDidChange(_ textView: UITextView) { let contents = textView.text.split(separator: "\n") guard !contents.isEmpty, let title = contents.first else { return } let body = contents.dropFirst().joined(separator: "\n") diary.title = "\(title)" diary.body = body } ``` ### ๐Ÿ”น alert๊ณผ swipe ์ฝ”๋“œ ๋ถ„๋ฆฌ ๋ฐ˜๋ณต์ ์ธ alertController ์ƒ์„ฑ ์ž‘์—…, ๊ทธ๋ฆฌ๊ณ  swipe ์ฝ”๋“œ๋ฅผ ๋ทฐ์ปจํŠธ๋กค๋Ÿฌ์—์„œ ์กฐ๊ธˆ์ด๋ผ๋„ ๋ถ„๋ฆฌํ•˜๋ฉด ์ข‹์„ ๊ฒƒ ๊ฐ™์•„ protocol๊ณผ extension์„ ํ™œ์šฉํ•˜์˜€์Šต๋‹ˆ๋‹ค. - `Alert` ๋ถ„๋ฆฌ์ „ ์ฝ”๋“œ ```swift private func showDeleteConfirmation() { let alertController = UIAlertController(title: "์ง„์งœ์š”?", message: "์ •๋ง๋กœ ์‚ญ์ œํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?", preferredStyle: .alert) let cancelAction = UIAlertAction(title: "์ทจ์†Œ", style: .cancel, handler: nil) alertController.addAction(cancelAction) let deleteAction = UIAlertAction(title: "์‚ญ์ œ", style: .destructive) { [weak self] _ in guard let self else { return } self.performDelete() } alertController.addAction(deleteAction) present(alertController, animated: true, completion: nil) } ``` - ๋ถ„๋ฆฌ ํ›„ ์ฝ”๋“œ ```swift protocol AlertDisplayable { func showAlert(title: String?, message: String?, actions: [UIAlertAction]) } extension AlertDisplayable where Self: UIViewController { func showAlert(title: String?, message: String?, actions: [UIAlertAction]) { let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert) actions.forEach { alertController.addAction($0) } present(alertController, animated: true, completion: nil) } } ``` - ๊ตฌํ˜„๋ถ€ ์ฝ”๋“œ ```swift private func showDeleteConfirmation() { let cancelAction = UIAlertAction(title: "์ทจ์†Œ", style: .cancel, handler: nil) let deleteAction = UIAlertAction(title: "์‚ญ์ œ", style: .destructive) { [weak self] _ in self?.performDelete() } showAlert(title: "์ง„์งœ์š”?", message: "์ •๋ง๋กœ ์‚ญ์ œํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?", actions: [cancelAction, deleteAction]) } ``` ### ๐Ÿ”น ์ผ๊ธฐ ๋ฆฌ์ŠคํŠธ ์ƒˆ๋กœ๊ณ ์นจ ์‹œ์  ์ฒ˜์Œ์—๋Š” `DiaryListViewController`์™€ `CreateDiaryViewController`๋ฅผ `delegate`๋กœ ์—ฐ๊ฒฐํ•˜์—ฌ ์ƒ์„ฑํ™”๋ฉด์ด pop๋  ๋•Œ ๋ฆฌ์ŠคํŠธ ํ™”๋ฉด์˜ `readCoreData` ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜๋„๋ก ๊ตฌํ˜„ํ•˜์˜€์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์ด๋ณด๋‹ค๋Š” ๋ทฐ์˜ ์ƒ๋ช…์ฃผ๊ธฐ๋ฅผ ํ™œ์šฉํ•˜๋Š” ํŽธ์ด ๋” ๊น”๋”ํ•  ๊ฒƒ ๊ฐ™๋‹ค๋Š” ์ƒ๊ฐ์ด ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค. ์ด์— ๋”ฐ๋ผ `viewWillAppear` ๋ฉ”์„œ๋“œ์—์„œ ์•„๋ž˜์™€ ๊ฐ™์ด ์ฒ˜๋ฆฌํ•ด์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค. ```swift override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) readCoreData() } ``` ## ์กฐ์–ธ์„ ์–ป๊ณ  ์‹ถ์€ ์  ### ๋นˆ ํ™”๋ฉด์—์„œ ๋’ค๋กœ๊ฐ€๊ธฐ๋ฅผ ๋ˆŒ๋ €์„ ๋•Œ์˜ ์ฒ˜๋ฆฌ๋ฐฉ๋ฒ• ์š”๊ตฌ์‚ฌํ•ญ์— ๋”ฐ๋ฅด๋ฉด, ์ผ๊ธฐ์žฅ์˜ + ๋ฒ„ํŠผ์„ ๋ˆŒ๋Ÿฌ ์ƒˆ ์ผ๊ธฐ์žฅ์„ ์ƒ์„ฑํ–ˆ์„ ๊ฒฝ์šฐ ํ‚ค๋ณด๋“œ๊ฐ€ ์ž๋™์œผ๋กœ ์˜ฌ๋ผ์˜ค๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๋’ค๋กœ๊ฐ€๊ธฐ ์ฒ˜๋ฆฌ๋ฅผ ํ•˜๊ฑฐ๋‚˜ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ƒํƒœ๋กœ ๋ณ€ํ™˜๋˜๋Š” ๋“ฑ ํ˜„์žฌ ํ™”๋ฉด์—์„œ ๋ฒ—์–ด๋‚˜๋ฉด ์ž‘์—…์ค‘์ธ ๋‚ด์šฉ์ด ์ž๋™์œผ๋กœ ์ €์žฅ์ด ๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ ‡๋‹ค๋ฉด + ๋ฒ„ํŠผ์„ ๋ˆŒ๋ €๋‹ค๊ฐ€ ์•„๋ฌด๋Ÿฐ ๋‚ด์šฉ์„ ์ž‘์„ฑํ•˜์ง€ ์•Š์€ ์ฑ„ ๋’ค๋กœ๊ฐ€๊ธฐ๋ฅผ ๋ˆŒ๋Ÿฌ๋„ ์ œ๋ชฉ/๋‚ด์šฉ์ด ์—†๋Š” ๋นˆ ์ผ๊ธฐ๊ฐ€ ์ƒ์„ฑ๋˜๋Š” ๊ฒƒ์ด ๋งž์„๊นŒ์š”? ์ €ํฌ๋Š” ์ด ๋ถ€๋ถ„์ด ์กฐ๊ธˆ ์–ด์ƒ‰ํ•œ ๊ฒƒ ๊ฐ™์•„์„œ ์•„์˜ˆ ๋น„์–ด์žˆ๋Š” ๋‚ด์šฉ์€ context์—์„œ save์ฒ˜๋ฆฌ๋ฅผ ํ•˜์ง€ ์•Š๋„๋ก ๋งŒ๋“ค์–ด๋‘์—ˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ฆฐ์˜ ์˜๊ฒฌ์ด ๊ถ๊ธˆํ•ฉ๋‹ˆ๋‹ค๐Ÿ˜ญ --- ### STEP2 ํ• ์ผ 1. Foundation์ด ๊ผญ ํ•„์š”ํ•œ๊ฐ€? ์•„๋‹ˆ๋ผ๋ฉด ์™œ์ผ๊นŒ? - CoreData์—์„œ ์ž์ฒด์ ์œผ๋กœ Foundation์„ importํ•˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์ง€์›Œ๋„ ์ƒ๊ด€์—†์Šต๋‹ˆ๋‹ค! ์ด ๋ถ€๋ถ„ ์‚ญ์ œ์ฒ˜๋ฆฌํ–ˆ์Šต๋‹ˆ๋‹ค. 2. NSManagedObject๊ฐ€ ์–ด๋–ค ์—ญํ• ์„ ํ•˜๋Š”๊ฐ€? - NSManagedObject๋Š” CoreData ๋ชจ๋ธ์„ ์‚ฌ์šฉํ•˜๊ฒŒ ๋  ๊ฒฝ์šฐ ์ƒ์†์„ ์ง„ํ–‰ํ•ด์•ผํ•˜๋Š” ๊ธฐ๋ณธ ํด๋ž˜์Šค์ž…๋‹ˆ๋‹ค. ์—”ํ‹ฐํ‹ฐ๋ช…, ์–ดํŠธ๋ฆฌ๋ทฐํŠธ๋ช…, ์–ดํŠธ๋ฆฌ๋ทฐํŠธ ๊ฐ„์˜ ๊ด€๊ณ„ ๋“ฑ์„ ํฌํ•จํ•œ (NSObject ๊ฐ์ฒด๋“ค์˜)๋ฉ”ํƒ€์ •๋ณด๋ฅผ ๊ฐ–๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. - [NSManagedObject](https://developer.apple.com/documentation/coredata/nsmanagedobject) 3. ๊ฐœํ–‰ ์ด์œ  - Diary์— ์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€๋ณด๋ ค๋‹ค๊ฐ€ ์‚ญ์ œํ•˜๋ฉด์„œ ์‹ค์ˆ˜๋กœ ๋‚จ๊ธด ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค. ๊ฐœํ–‰ ์‚ญ์ œํ–ˆ์Šต๋‹ˆ๋‹ค! - 4. Diary: Identifiable ์€ ํ•„์š”ํ•œ ๋‚ด์šฉ์ธ๊ฐ€? ์—†์œผ๋ฉด ์–ด๋–ค ๋ฌธ์ œ๊ฐ€ ์ƒ๊ธฐ๋‚˜? - Identifiable์€ ํ•ด๋‹น ํ”„๋กœํ† ์ฝœ์„ ์ฑ„ํƒํ•œ ํƒ€์ž…์—์„œ Hashable ๊ฐ’์œผ๋กœ id ํ”„๋กœํผํ‹ฐ๋ฅผ ๋งŒ๋“ค๋„๋ก ๊ฐ•์ œํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ๋ชจ๋“  ๊ฐ์ฒด๋ฅผ id๋กœ ๊ตฌ๋ถ„ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๊ธฐ ์œ„ํ•จ์ž…๋‹ˆ๋‹ค. ๋งŒ์•ฝ id๋กœ ๊ตฌ๋ถ„ํ•  ์ˆ˜ ์žˆ๋Š” ๊ฐ’์ด ์—†์„ ๊ฒฝ์šฐ ๋ฐ์ดํ„ฐ๋ฅผ ํŠน์ •ํ•  ์ˆ˜ ์—†์–ด ๋ฌธ์ œ๊ฐ€ ์ƒ๊ธฐ๋Š” ๊ฒƒ์œผ๋กœ ์•Œ๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ €ํฌ์˜ Diary๋Š” uuid๋กœ id ํ”„๋กœํผํ‹ฐ๋ฅผ ๊ฐ–๊ณ  ์žˆ๊ธฐ์—, ๊ธฐ๋Šฅ์ƒ ๋ฌธ์ œ๋Š” ๋ฐœ์ƒํ•˜์ง€ ์•Š๋Š” ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ Identifiable์˜ ์„ฑ์งˆ์„ ๋ช…์‹œ์ ์œผ๋กœ ๋ณด์—ฌ์ฃผ๋Š” ๊ฒƒ์ด ์ข‹์€ ๊ฒƒ ๊ฐ™์•„์„œ ์‚ญ์ œํ•˜์ง€ ์•Š๊ณ  ๊ทธ๋Œ€๋กœ ๋‘์—ˆ์Šต๋‹ˆ๋‹ค! 5. ShareDiary ํ”„๋กœํ† ์ฝœ ๋„ค์ด๋ฐ: ShareDisplayable (Alert ํŒŒ์ผ๋ช…๋„ ์ˆ˜์ •) - ๋‘ ๊ฐ€์ง€ ๋„ค์ด๋ฐ์„ ๋น„์Šทํ•˜๊ฒŒ ๋งž์ถ”๋Š” ํŽธ์ด ์ข‹์•˜์„ ๊ฒƒ ๊ฐ™๋„ค์š”...!! ์ˆ˜์ • ์ง„ํ–‰ํ•˜์˜€์Šต๋‹ˆ๋‹ค! 6. shareText ๊ฐ€๋…์„ฑ ๋†’์ด๋Š” ๋ฐฉ๋ฒ• - ๊ทธ๋ฆฐ๊ป˜์„œ ๋ง์”€ํ•˜์‹  ์ˆ˜์ •์ ์ด ์–ด๋”œ๊นŒ์— ๋Œ€ํ•˜์—ฌ ๋งŽ์€ ๊ณ ๋ฏผ์„ ํ•˜์˜€์Šต๋‹ˆ๋‹ค ๐Ÿ˜ข ์ฒ˜์Œ์—๋Š” shareํ•  ํ•ญ๋ชฉ์„ ๋”ฐ๋กœ ์ •๋ฆฌํ•ด๋‘์–ด์•ผ ํ•˜๋‚˜? ๋ผ๋Š” ์ƒ๊ฐ๋„ ํ–ˆ๋Š”๋ฐ, ๊ณ ๋ฏผ ๋์— '๊ฐ€๋…์„ฑ'์— ์ดˆ์ ์„ ๋งž์ถ”๋Š” ๊ฒƒ์œผ๋กœ ๊ฒฐ๋ก ์„ ๋‚ด๋ ธ์Šต๋‹ˆ๋‹ค.(ํ˜น์‹œ ์˜๋„ํ•˜์‹  ๋ฐ”๊ฐ€ ์ด๊ฒŒ ์•„๋‹ˆ์‹œ๋ผ๋ฉด ์ถ”๊ฐ€๋กœ ๋ง์”€ ๋ถ€ํƒ๋“œ๋ ค์š”!!) ์ด์— ๋”ฐ๋ผ ๊ธฐ์กด์—๋Š” ํ•œ ์ค„๋กœ ์ž‘์„ฑ๋˜์—ˆ๋˜ ๋ถ€๋ถ„์„ ์—ฌ๋Ÿฌ ์ค„ String ํ˜•์‹์œผ๋กœ ๋ณ€๊ฒฝํ•˜์—ฌ ์ž‘์„ฑํ•˜์˜€์Šต๋‹ˆ๋‹ค. ```swift let shareText = "์ œ๋ชฉ: \(title)\n์ž‘์„ฑ์ผ์ž: \(date)\n๋‚ด์šฉ: \(body)" ``` ```swift let shareText = """ ์ œ๋ชฉ: \(title) ์ž‘์„ฑ์ผ์ž: \(date) ๋‚ด์šฉ: \(body) """ ``` 7. preferredStyle ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋ณด๋‚ด๊ธฐ - ์ฝ”๋“œ ์ถ”๊ฐ€ ํ›„ ๋ณ€๊ฒฝ ์™„๋ฃŒ ํ–ˆ์Šต๋‹ˆ๋‹ค ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค!! 8. Create, delete ์—๋Ÿฌ ๋ฐœ์ƒํ•˜์ง€ ์•Š๋Š”์ง€? - create ์‹œ์—๋Š” ๋‹จ์ˆœํžˆ ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑ๋งŒ ํ•˜๋Š” ๊ฒƒ์ด๋ผ ์˜ค๋ฅ˜๊ฐ€ ๋‚˜์ง€ ์•Š์ง€๋งŒ, delete ์‹œ์—๋Š” ์˜ค๋ฅ˜๊ฐ€ ๋‚  ์ˆ˜ ์žˆ์„ ๊ฒƒ ๊ฐ™์•„์„œ ์—๋Ÿฌํƒ€์ž…์„ ์ถ”๊ฐ€ํ•˜์˜€์Šต๋‹ˆ๋‹ค. 9. Fatalerror ์‚ฌ์šฉ ์ด์œ  -> ๊ทธ๋Œ€๋กœ ๊ธ์–ด์™€์„œ์ž„ ใ…  - persistentContainer์˜ ๊ฒฝ์šฐ ์‹ฑ๊ธ€ํ†ค ๊ฐ์ฒด ๋‚ด๋ถ€ ํ”„๋กœํผํ‹ฐ ํ˜•์‹์œผ๋กœ ๋˜์–ด ์žˆ์–ด, ์ถ”๊ฐ€ ์˜ค๋ฅ˜์ฒ˜๋ฆฌ๊ฐ€ ์–ด๋ ต๋‹ค๊ณ  ํŒ๋‹จ๋˜์–ด fatalError๋ฅผ ์‚ฌ์šฉํ•˜์˜€์Šต๋‹ˆ๋‹ค. ํ•ด๋‹น ๋ถ€๋ถ„์—์„œ ์˜ค๋ฅ˜๊ฐ€ ๋‚œ๋‹ค๋ฉด ์ €์žฅ์†Œ ์ž์ฒด๋ฅผ ๋ถˆ๋Ÿฌ์˜ค์ง€ ๋ชปํ•œ ์ผ€์ด์Šค๋ผ์„œ ์ •์ƒ์ ์œผ๋กœ ์•ฑ์ด ์ž‘๋™ํ•˜์ง€ ๋ชปํ•  ๊ฒƒ์ด๋ผ๊ณ  ์ƒ๊ฐ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๋ฐ˜๋ฉด saveContext์— ์žˆ๋˜ fatalError์˜ ๊ฒฝ์šฐ๋Š” ์ž์ฒด์ ์œผ๋กœ save ์‹คํŒจ ์˜ค๋ฅ˜๋ฅผ ๋งŒ๋“ค์–ด ๋ทฐ์ปจํŠธ๋กค๋Ÿฌ๋กœ throwํ•ด์ฃผ๋ฉด ์•ฑ์„ ๊ฐ•์ œ์ข…๋ฃŒ์‹œํ‚ค๋Š” ๋Œ€์‹  ์–ผ๋Ÿฟ ์ฒ˜๋ฆฌ๋กœ ๋Œ€์ฒดํ•  ์ˆ˜ ์žˆ์„ ๊ฒƒ ๊ฐ™์•„์„œ ์ˆ˜์ • ์ง„ํ–‰ํ–ˆ์Šต๋‹ˆ๋‹ค! 10. ๊ฐœํ–‰ ์œ ์ง€ํ•˜๊ธฐ (ํ•˜๋‚˜๋งŒ components๋กœ ๋ฐ”๊พธ๊ธฐ & cell์— ๋ณด๋‚ด๊ธฐ ์ „์— ์ฒ˜๋ฆฌ ์ถ”๊ฐ€) - ๋ง์”€ ๋“ฃ๊ณ  ๋ณด๋‹ˆ ์˜๋„์ ์ธ ๊ฐœํ–‰์€ ์œ ์ง€ํ•˜๋Š” ๊ฒƒ์ด ์ข‹์„ ๊ฒƒ ๊ฐ™์•„์„œ ์ˆ˜์ • ์ง„ํ–‰ํ•˜์˜€์Šต๋‹ˆ๋‹ค! 11. ๋„ค์ž„์ŠคํŽ˜์ด์Šค ๋งŒ๋“ค๊ธฐ - ๋„ค์ž„์ŠคํŽ˜์ด์Šค๋ฅผ ์“ฐ๋ฉด ๊ด€๋ฆฌ์˜ ์šฉ์ด์„ฑ์€ ์˜ฌ๋ผ๊ฐ€์ง€๋งŒ ์ฝ”๋“œ์˜ ๊ฐ€๋…์„ฑ์ด ์˜คํžˆ๋ ค ๋–จ์–ด์ง€๋Š” ๋‹จ์ ๋„ ํ•จ๊ป˜ ์˜ค๋Š” ๊ฒƒ ๊ฐ™์•„์„œ, ์ฒ˜์Œ์—๋Š” ๋„ค์ž„์ŠคํŽ˜์ด์Šค ์—†์ด ์ž‘์—…์„ ํ–ˆ์—ˆ์Šต๋‹ˆ๋‹ค...! ๊ทธ๋ฆฐ์˜ ์˜๊ฒฌ์„ ๋“ฃ๊ณ  ๊ณ ๋ฏผ์„ ํ•ด๋ณด๋‹ค๊ฐ€ ์ผ๋‹จ ๋งค์ง ๋ฆฌํ„ฐ๋Ÿด๋“ค์„ ๋„ค์ž„์ŠคํŽ˜์ด์Šค๋กœ ๊ตฌ๋ถ„ํ•˜์—ฌ ์˜ฎ๊ฒจ๋ณด์•˜์Šต๋‹ˆ๋‹ค. (๋‹ค๋งŒ ์˜ค๋ฅ˜์™€ ๊ด€๋ จ๋œ alert ๋‚ด์šฉ์„ ๋ชจ๋‘ ์˜ค๋ฅ˜ ํƒ€์ž…์œผ๋กœ ์˜ฎ๊ฒผ๋”๋‹ˆ ๋„ค์ž„์ŠคํŽ˜์ด์Šค์˜ ๊ตฌ๋ถ„์ด ๋‹ค์†Œ ์• ๋งคํ•ด์ง„ ๊ฒƒ ๊ฐ™๋„ค์š”๐Ÿ˜ญ) 12. ๊ฐ•์ข… ํ…Œ์ŠคํŠธ -> ์–ด๋–ป๊ฒŒ ํ• ์ง€? - SceneDelegate์˜ applicationWillTerminate ๋ฉ”์„œ๋“œ์— ์ €์žฅ ๋‚ด์šฉ์„ ์ถ”๊ฐ€ํ•ด ๋ณด์•˜๋Š”๋ฐ, ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ ์ž์ฒด๋ฅผ ๊ฐ•์ œ์ข…๋ฃŒ์‹œํ‚ค๋Š” ์‹์œผ๋กœ ํ…Œ์ŠคํŠธ๋ฅผ ์ง„ํ–‰ํ–ˆ๋”๋‹ˆ ์•ฑ๋งŒ ๊ฐ•์ œ์ข…๋ฃŒ๋  ๊ฒฝ์šฐ์— ๋Œ€ํ•œ ํ…Œ์ŠคํŠธ๊ฐ€ ์ œ๋Œ€๋กœ ์ด๋ฃจ์–ด์ง€์ง€ ์•Š๋Š” ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค๐Ÿ˜ญ ํ™•์ธ์ด ์–ด๋ ค์›Œ์„œ ํ•ด๋‹น ๋‚ด์šฉ์€ ๋‹ค์‹œ ์›๋ณตํ•ด ๋‘์—ˆ์Šต๋‹ˆ๋‹ค. ```swift func applicationWillTerminate(_ application: UIApplication) { CoreDataManager.shared.saveContext() } ``` 13. createVC ๋„ค์ด๋ฐ: DiaryDetailViewContoller - ๋‘ ๊ฐ€์ง€ ๋‹ค๋ฅธ ์ž‘์—…์„ ์ง„ํ–‰ํ•˜๋Š” ๋ทฐ์ปจํŠธ๋กค๋Ÿฌ์˜ ๋„ค์ด๋ฐ์„ ์ •ํ•˜๊ธฐ๊ฐ€ ์‰ฝ์ง€ ์•Š์•„์„œ ๊ณ ๋ฏผ์ด ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค๐Ÿ˜ญ ๊ฒฐ๊ณผ์ ์œผ๋กœ List๋ผ๋Š” ์ด๋ฆ„์— ๋Œ€๋น„๋˜๋Š” Detail์ด๋ผ๋Š” ๋ช…์นญ์„ ํ™œ์šฉํ•˜์˜€์Šต๋‹ˆ๋‹ค. --- # PR ## STEP 3 > ์ผ๊ธฐ์žฅ [STEP 3] Hamg, maxhyunm ์•ˆ๋…•ํ•˜์„ธ์š” ๊ทธ๋ฆฐ!(@GREENOVER) ์ผ๊ธฐ์žฅ STEP 3 PR ์˜ฌ๋ ค๋“œ๋ฆฝ๋‹ˆ๋‹ค. ์ด๋ฒˆ ์Šคํ…๋„ ์ž˜ ๋ถ€ํƒ๋“œ๋ฆฝ๋‹ˆ๋‹ค๐Ÿ™‡โ€ ## ๊ณ ๋ฏผํ–ˆ๋˜ ์  ### ๐Ÿ”น CoreLocation ํ™œ์šฉ #### CoreLocation์„ ํ†ตํ•ด ์ •๋ณด๋ฅผ ๋ฐ›์•„์˜ค๋Š” ์œ„์น˜ ์‹ค์งˆ์ ์œผ๋กœ Location ์ •๋ณด๊ฐ€ ํ•„์š”ํ•œ ๊ฒƒ์€ DiaryDetailViewController์—์„œ ๋‚ ์”จ API๋ฅผ ํ˜ธ์ถœํ•  ๋•Œ์ž…๋‹ˆ๋‹ค. ๊ทธ๋ ‡์ง€๋งŒ DiaryDetailViewController์—์„œ ์œ„์น˜์ •๋ณด ํ™œ์šฉ ๋™์˜๋ฅผ ๋ฐ›๊ณ  ์—…๋ฐ์ดํŠธ๋ฅผ ์ง„ํ–‰ํ•˜๋Š” ๊ฒƒ๋ณด๋‹ค๋Š” ์•ฑ์˜ ์ฒซ ํ™”๋ฉด์—์„œ ํ•ด๋‹น ๋‚ด์šฉ์„ ์ง„ํ–‰ํ•˜๋Š” ํŽธ์ด ๋” ์ž์—ฐ์Šค๋Ÿฌ์šธ ๊ฒƒ ๊ฐ™๋‹ค๊ณ  ํŒ๋‹จ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์ด์— ๋”ฐ๋ผ ์œ„์น˜ ์ •๋ณด ์—…๋ฐ์ดํŠธ ์ž์ฒด๋Š” ์ฒซ ํ™”๋ฉด์ธ DiaryListViewController์—์„œ ์ง„ํ–‰ํ•˜๊ณ , DiaryDetailViewController์—์„œ๋Š” API ํ†ต์‹ ์— ํ•„์š”ํ•œ ์œ„๋„, ๊ฒฝ๋„ ๋ฐ์ดํ„ฐ๋งŒ ๋„˜๊ฒจ๋ฐ›์„ ์ˆ˜ ์žˆ๋„๋ก ๊ตฌํ˜„ํ•˜์˜€์Šต๋‹ˆ๋‹ค. ```swift let createDiaryView = DiaryDetailViewController(latitude: self.latitude, longitude: self.longitude) self.navigationController?.pushViewController(createDiaryView, animated: true) ``` ๋˜ํ•œ ์œ„์น˜์ •๋ณด ํ™œ์šฉ์— ๋™์˜ํ•˜์ง€ ์•Š์€ ๊ฒฝ์šฐ์—๋„ ์ผ๊ธฐ ์ž์ฒด๋Š” ์ž‘์„ฑ ๊ฐ€๋Šฅํ•˜๋„๋ก ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•ด(๋‚ ์”จ ์ด๋ชจํ‹ฐ์ฝ˜๋งŒ ์ œ์™ธ) ์œ„๋„, ๊ฒฝ๋„ ๋ฐ์ดํ„ฐ๋Š” nil๋กœ๋„ ์ „๋‹ฌ๋  ์ˆ˜ ์žˆ๋„๋ก ํ•˜์˜€์Šต๋‹ˆ๋‹ค. ```swift init(latitude: Double?, longitude: Double?) { self.diary = CoreDataManager.shared.createDiary() self.isNew = true self.latitude = latitude self.longitude = longitude super.init(nibName: nil, bundle: nil) fetchWeather() } ``` #### ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ์˜ ์œ„์น˜ ์ •๋ณด ์„ค์ • ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ๋กœ CoreLocation ๊ธฐ๋Šฅ์„ ํ…Œ์ŠคํŠธํ•˜๋ฉด ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ ์ž์ฒด์— ์„ค์ •๋œ Location ์ •๋ณด์— ๋”ฐ๋ผ ์œ„์น˜๋ฅผ ํ‘œ์‹œํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ์ด ์„ค์ •์ด None์œผ๋กœ ๋˜์–ด์žˆ์„ ๊ฒฝ์šฐ์—๋Š” ์œ„์น˜๊ฐ€ ์ •์ƒ์ ์œผ๋กœ ๋ถˆ๋Ÿฌ์™€์ง€์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ด ์‚ฌ์‹ค์„ ๊ฐ„๊ณผํ•˜์—ฌ ํ…Œ์ŠคํŠธ ๊ณผ์ •์—์„œ ๋งŽ์€ ์‹œํ–‰์ฐฉ์˜ค๋ฅผ ๊ฑฐ์ณค์Šต๋‹ˆ๋‹ค. ๊ฒฐ๊ณผ์ ์œผ๋กœ๋Š” Custom Location์„ ํ™œ์šฉํ•˜์—ฌ ์ •์ƒ์ ์œผ๋กœ ํ…Œ์ŠคํŠธ๋ฅผ ์ง„ํ–‰ํ•  ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. <img src="https://hackmd.io/_uploads/BJ20Xkgyp.png" width="500"> <br/> ### ๐Ÿ”น ์•„์ด์ฝ˜ ์บ์‹ฑ ํ˜„์žฌ ์ €ํฌ์˜ ์ผ๊ธฐ์žฅ ์•ฑ์€ ๋ชจ๋“  ์…€์ด ์„œ๋ฒ„ํ†ต์‹ ์„ ํ†ตํ•ด ์•„์ด์ฝ˜์„ ๊ฐ€์ง€๊ณ  ์˜ค๋„๋ก ๊ตฌํ˜„๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ๋‚ ์”จ ์•„์ด์ฝ˜์€ ๋ช‡ ๊ฐœ์˜ ์ •ํ•ด์ง„ ์•„์ด์ฝ˜์ด ๋ฐ˜๋ณต๋˜์–ด ํ™œ์šฉ๋˜๊ณ  ์žˆ๊ธฐ์—, ์ด๋ฏธ์ง€ ์บ์‹ฑ์œผ๋กœ ์ €์žฅํ•˜์—ฌ ๋ฐ”๋กœ ๋ณด์—ฌ์ฃผ๋ฉด ์ข‹๊ฒ ๋‹ค๊ณ  ์ƒ๊ฐํ–ˆ์Šต๋‹ˆ๋‹ค. ```swfit class ImageCachingManager { static let shared = NSCache<NSString, UIImage>() ... } ``` ```swift switch result { case .success(let data): guard let image = UIImage(data: data) else { return } DispatchQueue.main.async { ImageCachingManager.shared.setObject(image, forKey: NSString(string: icon)) self?.weatherIconImageView.image = image } ``` ## ์กฐ์–ธ์ด ํ•„์š”ํ•œ ์  ### ๐Ÿ”น DTO ํ™œ์šฉ ๋‚ ์”จ ์„œ๋ฒ„์˜ API์—๋Š” ์ €ํฌ๊ฐ€ ์‚ฌ์šฉํ•˜๊ณ ์ž ํ•˜๋Š” `Weather`๋ฅผ ์ œ์™ธํ•˜๊ณ ๋„ ๋งŽ์€ ๋‚ด์šฉ์ด ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ์ €ํฌ๋Š” ์ด๋ฅผ ํ™œ์šฉํ•˜๊ธฐ ์œ„ํ•ด ์ผ๋‹จ API์—์„œ ์ „๋‹ฌ๋ฐ›๋Š” ๋ชจ๋“  ๋‚ด์šฉ์„ Decodingํ•  ์ˆ˜ ์žˆ๋„๋ก ๊ด€๋ จ ํƒ€์ž…์„ ๋ชจ๋‘ ์ƒ์„ฑํ•˜์˜€์Šต๋‹ˆ๋‹ค. ```swift struct WeatherResult: Decodable { let coord: Coord let weather: [Weather] let base: String let main: Main let visibility: Int let wind: Wind let clouds: Clouds let date: Int ...... } ``` ํ•˜์ง€๋งŒ ๋ชจ๋“  ๋‚ด์šฉ์„ ๊ตฌํ˜„ํ•˜์ง€ ์•Š๊ณ  ์•„๋ž˜์™€ ๊ฐ™์ด ํ•„์š”ํ•œ ์ •๋ณด๋งŒ ๊ณจ๋ผ DTO ํƒ€์ž…์„ ๋งŒ๋“ค์–ด๋„ ์ •์ƒ์ ์œผ๋กœ ๋ฐ์ดํ„ฐ๊ฐ€ ๋ถˆ๋Ÿฌ์™€์ง€๋Š” ๊ฒƒ์„ ํ™•์ธํ•˜์˜€์Šต๋‹ˆ๋‹ค. ```swift struct WeatherResult: Decodable { let weather: [Weather] } struct Weather: Decodable { let id: Int let main: String let description: String let icon: String } ``` ์—ฌ๊ธฐ์„œ ์ „์ฒด๋ฅผ ๋‹ค ์ƒ์„ฑํ•˜์—ฌ ์œ ์—ฐ์„ฑ ์žˆ๊ฒŒ ์‚ฌ์šฉํ• ์ง€ ์•„๋‹ˆ๋ฉด ํ•„์š”ํ•œ ๋ถ€๋ถ„๋งŒ ๋งŒ๋“ค์–ด์„œ ์‚ฌ์šฉํ•ด๋„ ๋˜๋Š” ๊ฒƒ์ธ์ง€ ๊ณ ๋ฏผ์ด ๋ฉ๋‹ˆ๋‹ค. ๋ณดํŽธ์ ์œผ๋กœ ์–ด๋–ป๊ฒŒ ์„ ํƒํ•˜์‹œ๋Š”์ง€ ๊ถ๊ธˆํ•ฉ๋‹ˆ๋‹ค!