# ios-cantact-manager-UI ## ๐Ÿ’ฐTeam BUJA ๐Ÿƒ๐Ÿป๐Ÿƒ๐Ÿปโ€โ™‚๏ธ๐Ÿ’จ **ํ”„๋กœ์ ํŠธ ๊ธฐ๊ฐ„:** `23.01.30` ~ `23.02.10` |<img src="https://avatars.githubusercontent.com/u/71758542?v=4" width=200 style="border-radius: 70%">|<img src="https://avatars.githubusercontent.com/u/92699723?v=4" width=200 style="border-radius: 70%">| |:---:|:---:| |[Blu](https://github.com/bomyuniverse)|[Jason](https://github.com/JasonLee0223)| ---- [[(Ver.Console) Previous Project] ios-contact-manager](https://github.com/calledBlu/ios-cantact-manager) <a href ="#1-Step1---iOS App Target ์ถ”๊ฐ€">Step1 - iOS App Target ์ถ”๊ฐ€</a> <a href ="#2-Step2---์—ฐ๋ฝ์ฒ˜ ๋ชฉ๋ก ๊ตฌํ˜„">Step2 - ์—ฐ๋ฝ์ฒ˜ ๋ชฉ๋ก ๊ตฌํ˜„</a> <a href ="#3-Step3---์—ฐ๋ฝ์ฒ˜ ์ถ”๊ฐ€ ๊ธฐ๋Šฅ ๊ตฌํ˜„ ">Step3 - ์—ฐ๋ฝ์ฒ˜ ์ถ”๊ฐ€ ๊ธฐ๋Šฅ ๊ตฌํ˜„ </a> ---- ## ๐Ÿ”– ์—ญํ•  ๋ถ„๋ฐฐ |Controller|์—ญํ• | |:---|:---| |ContactManagerTableViewController|- ํ”„๋กœ์ ํŠธ ์‹คํ–‰์‹œ ์ฒซ ํ™”๋ฉด <br/>- ์šฐ์ธก ์ƒ๋‹จ์˜ '+'๋ฒ„ํŠผ์„ ํ†ตํ•ด AddNewContactViewController ํ™”๋ฉด์œผ๋กœ Modalํ˜•ํƒœ๋กœ ๋„˜์–ด๊ฐ„๋‹ค. <br/>- ์ƒˆ๋กœ์šด ์—ฐ๋ฝ์ฒ˜๊ฐ€ ์ €์žฅ๋˜๋ฉด reload()๋ฅผ ํ†ตํ•ด ์—…๋ฐ์ดํŠธํ•œ๋‹ค.| |AddNewContactViewController|- ์ด๋ฆ„, ๋‚˜์ด, ์—ฐ๋ฝ์ฒ˜ ์ˆœ์„œ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์ž…๋ ฅํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•œ๋‹ค. <br/>- ํ™”๋ฉด ์ƒ๋‹จ์˜ ์ขŒ,์šฐ์ธก์œผ๋กœ ์ €์žฅํ•  ์ˆ˜ ์žˆ๊ฑฐ๋‚˜ ์ž…๋ ฅ์„ ์ทจ์†Œํ•˜๊ณ  ์ฒซ ํ™”๋ฉด์œผ๋กœ ๋Œ์•„๊ฐ„๋‹ค.| |View|์—ญํ• | |:---|:---| |UITableView|TableView ํ˜•ํƒœ๋กœ ์—ฐ๋ฝ์ฒ˜์˜ ๋ชฉ๋ก์„ ๋ณด์—ฌ์ค€๋‹ค.| |Alert|์‚ฌ์šฉ์ž ์ž…๋ ฅ์— ๋งž์ถฐ ์ž˜๋ชป์ž…๋ ฅํ•˜๋ฉด ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์ž…๋ ฅํ•˜๋ผ๋Š” ๊ฒฝ๊ณ ๋ฌธ์„ Alertํ˜•ํƒœ๋กœ ๋„์›Œ์ค€๋‹ค.| |struct/class|์—ญํ• | |:---|:---| |JSONManangement|Local File์ธ `dummy.json`ํŒŒ์ผ์„ ๋ถˆ๋Ÿฌ์™€์„œ ContactInformation์˜ JSONํ˜•ํƒœ๋กœ Decodingํ•˜์—ฌ Loadํ•ด์ค€๋‹ค.| |enum|์—ญํ• | |:---|:---| |JSONErrors|JSON Parsing ๊ณผ์ • ์ค‘ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜๋Š” ์ผ€์ด์Šค๋ฅผ ์ •์˜ ๋ฐ ํ˜ธ์ถœ๋œ๋‹ค.| |protocol|์—ญํ• | |:---|:---| |ReusableTableViewCell|ReusableTableViewCellํƒ€์ž…๊ณผ ๋งž๋Š” Cell๋งŒ์„ ์žฌ์‚ฌ์šฉํ•˜๋„๋ก ํ•œ๋‹ค.| |SendContactDataDelegate|delegate๋ฅผ ํ†ตํ•ด ์‚ฌ์šฉ์ž๊ฐ€ Save ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋ฉด ContactInformationํ˜•ํƒœ๋กœ ์ €์žฅํ•œ๋‹ค.| |JSONParsable|JSON Parsing์— ๊ด€ํ•œ ๋ฉ”์„œ๋“œ ์ •์˜| ---- ## Step1 - iOS App Target ์ถ”๊ฐ€ - ์ด์ „ ํ”„๋กœ์ ํŠธ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ UIKit App Target ์ถ”๊ฐ€ - ContactManager ํƒ€๊นƒ์˜ ์ฃผ์š” ํŒŒ์ผ์„ ContactManagerUI์˜ ํƒ€๊นƒ ๋ฉค๋ฒ„์‹ญ ํŒŒ์ผ๋กœ ์ถ”๊ฐ€ ๊ธฐ์กด์˜ ํ”„๋กœ์ ํŠธ์— UI ๋ฒ„์ „์„ ์ถ”๊ฐ€ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•ด ์„ ํ–‰๋˜์–ด์•ผ ํ•˜๋Š” ๋‹จ๊ณ„๋กœ, ์•ฑ ํƒ€๊ฒŸ ์„ค์ • ๊ณผ์ •์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค. 1. ContactManager์˜ ํ”„๋กœ์ ํŠธ ์„ค์ •์—์„œ TARGETS ๋ชฉ๋ก ํ•˜๋‹จ์˜ + ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅธ๋‹ค. 2. iOS-App์„ ์„ ํƒํ•œ๋‹ค 3. ContactManagerUI๋กœ ๋„ค์ด๋ฐ ํ›„ ํƒ€๊ฒŸ์„ ์ƒ์„ฑํ•œ๋‹ค. 4. ContactManager์˜ ์ฃผ์š” ํŒŒ์ผ์˜ ์ธ์ŠคํŽ™ํ„ฐ์—์„œ ContactManagerUI๋ฅผ ํƒ€๊นƒ ๋ฉค๋ฒ„์‹ญ ํŒŒ์ผ๋กœ ์ถ”๊ฐ€ํ•œ๋‹ค. ## Step2 - ์—ฐ๋ฝ์ฒ˜ ๋ชฉ๋ก ๊ตฌํ˜„ [PR #7 | Step1~2 - iOS App Target ์ถ”๊ฐ€ ๋ฐ ์—ฐ๋ฝ์ฒ˜ ๋ชฉ๋ก ๊ตฌํ˜„](https://github.com/tasty-code/ios-contact-manager-ui/pull/7#issue-1563831481) - Table View๋ฅผ ํ™œ์šฉํ•˜์—ฌ ์—ฐ๋ฝ์ฒ˜ ๋ชฉ๋ก์„ ํ™”๋ฉด์— ํ‘œ์‹œ - ๊ฐ ํ–‰์˜ cell์— subtitle style ์ ์šฉ - ํ•™ ํ–‰์— ์ด๋ฆ„, ๋‚˜์ด, ์—ฐ๋ฝ์ฒ˜ ํ‘œ์‹œ - Dummy Data๋ฅผ JSON์œผ๋กœ ๊ตฌํ˜„ํ•˜์—ฌ Decoding ํ›„Table View Data์— ์ ์šฉ - Protocol & Generic์„ ์‚ฌ์šฉํ•˜์—ฌ cell identifier๋ฅผ ์ง์ ‘ ์ž…๋ ฅํ•˜์ง€ ์•Š๊ณ  ๋ฉ”ํƒ€ํƒ€์ž…์œผ๋กœ ์ถ”๋ก ํ•˜์—ฌ ์žฌ์‚ฌ์šฉํ•˜๋„๋ก ๊ตฌํ˜„ UITableView๋ฅผ ์ด์šฉํ•˜์—ฌ ์—ฐ๋ฝ์ฒ˜๋“ค์„ ํ™”๋ฉด์— ํ‘œ์‹œํ•˜๋Š” ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•˜๋Š” ๋‹จ๊ณ„์ด๋‹ค. Custom cell์„ ์ƒ์„ฑํ•˜์ง€ ์•Š๊ณ , ์Šคํ† ๋ฆฌ๋ณด๋“œ์—์„œ ๊ธฐ๋ณธ์ ์œผ๋กœ ์ œ๊ณตํ•˜๋Š” Prototype cell์„ ์‚ฌ์šฉํ•˜์˜€๋‹ค. iOS 14๋ถ€ํ„ฐ `cell.textLabel?.text` ํ˜•ํƒœ๋กœ ์ง์ ‘ ์ ‘๊ทผํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹Œ content configuration์„ ํ†ตํ•˜์—ฌ cell์„ ๊ตฌ์„ฑํ•˜๋„๋ก ๊ฐœํŽธ๋˜์–ด์„œ ํ•ด๋‹น ๋ถ€๋ถ„์„ ์ ์šฉํ•ด ๋ณด์•˜๋‹ค. ### ๐Ÿš€ ์ ์šฉํ•˜๋ ค๊ณ  ๋…ธ๋ ฅํ•ด๋ณธ ์  ํ”„๋กœ์ ํŠธ ๊ธฐ๊ฐ„๋™์•ˆ JSON์— ๋Œ€ํ•ด ํ•™์Šตํ•˜๊ฒŒ๋˜์–ด ์•„์ง Server๋กœ ๋ถ€ํ„ฐ API ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„์˜ค์ง€๋Š” ๋ชปํ•˜์ง€๋งŒ FilePath๋ฅผ ํ†ตํ•ด JSON Parshing์„ ๊ฒฝํ—˜ํ•ด๋ณด๊ณ ์žํ•˜์˜€๋‹ค! ์ฒ˜์Œ์€ Codable์ด ๋‚˜์˜ค๊ธฐ ์ด์ „์— ์‚ฌ์šฉํ–ˆ๋˜ `JSONSerialization`์„ ๊ฒฝํ—˜ํ•ด๋ณด๊ณ  `JSONDecoder`๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ฆฌํŽ™ํ† ๋งํ•ด๋ณด๋Š” ๊ฒฝํ—˜์„ ํ•ด๋ณด์•˜๋‹ค. ```Swift // JSONSerialization ์‚ฌ์šฉ private func testJSON() { guard let filePath = Bundle.main.url(forResource: "Dummy", withExtension: "json") else { return } if let data = try? Data(contentsOf: filePath) { let json = try! JSONSerialization.jsonObject(with: data, options: []) as! [String: Any] contactInfomation = json } if let contactInfo = contactInfomation["Dummy"] as? [[String: Any]] { contactInfo.forEach { contactData in nameArr.append(contactData["name"] as! String) ageArr.append(contactData["age"] as! String) phoneNumberArr.append(contactData["phoneNumber"] as! String) } } ``` ```Swift // JSONDecoder ์‚ฌ์šฉ func loadJSON<T>(_ filename: String) throws -> T where T : Decodable { let data: Data guard let filePath = Bundle.main.url(forResource: filename, withExtension: nil) else { print("\(filename) not found.") throw JSONErrors.notFoundJSONFile } do { data = try Data(contentsOf: filePath) } catch { print("Could not load \(filename): (error)") throw JSONErrors.notLoadData } do { let JSONDecoder = JSONDecoder() return try JSONDecoder.decode(T.self, from: data) } catch { print("Unable to decode \(filename): (error)") throw JSONErrors.unableToDecode } } ``` ## Step3 - ์—ฐ๋ฝ์ฒ˜ ์ถ”๊ฐ€ ๊ธฐ๋Šฅ ๊ตฌํ˜„ [PR #19 | Step3 - ์—ฐ๋ฝ์ฒ˜ ์ถ”๊ฐ€ ๊ธฐ๋Šฅ ๊ตฌํ˜„](https://github.com/tasty-code/ios-contact-manager-ui/pull/19) - ๋„ค๋น„๊ฒŒ์ด์…˜ ์ปจํŠธ๋กค๋Ÿฌ ๊ตฌํ˜„ - ์—ฐ๋ฝ์ฒ˜ ๋ชฉ๋ก ํ™”๋ฉด์˜ ์šฐ์ƒ๋‹จ + ๋ฒ„ํŠผ์„ ํ†ตํ•ด ์—ฐ๋ฝ์ฒ˜ ์ถ”๊ฐ€ ํ™”๋ฉด์œผ๋กœ ์ง„์ž…(Modal) - AutoLayout ๊ณผ StackView๋ฅผ ํ™œ์šฉํ•˜์—ฌ ๋ ˆ์ด์•„์›ƒ ๊ตฌ์„ฑ - ๊ฐ ํ•„๋“œ์— ๋งž๋Š” ํ‚ค๋ณด๋“œ ์ข…๋ฅ˜ ์ง€์ • - ์ด๋ฆ„: ASCII Capable - ๋‚˜์ด: Number Pad - ์—ฐ๋ฝ์ฒ˜: Phone Pad - ์ƒˆ ์—ฐ๋ฝ์ฒ˜ ์ถ”๊ฐ€ ํ™”๋ฉด์—์„œ ๋„ค๋น„๊ฒŒ์ด์…˜ ๋ฐ”์˜ ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋Š” ๊ฒฝ์šฐ ์•Œ๋ฆผ์ฐฝ์ด ๋‚˜ํƒ€๋‚˜๋„๋ก ๊ตฌํ˜„ - ์ทจ์†Œ ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋ฉด ์ •๋ง ์ทจ์†Œํ•  ๊ฒƒ์ธ์ง€ ๋ฌป๋„๋ก ์•Œ๋ฆผ์ฐฝ ๊ตฌํ˜„ - ์ €์žฅ ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋ฉด ์ž…๋ ฅํ•œ ์ •๋ณด๊ฐ€ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์ž…๋ ฅ๋˜์—ˆ๋Š”์ง€ ํ™•์ธํ•˜๊ณ  ์˜ฌ๋ฐ”๋ฅด์ง€ ์•Š์€ ๊ฒฝ์šฐ ํ•ด๋‹น ์ •๋ณด๋ฅผ, ์˜ฌ๋ฐ”๋ฅธ ๊ฒฝ์šฐ ์—ฐ๋ฝ์ฒ˜ ๋ชฉ๋ก ํ™”๋ฉด์œผ๋กœ ์ด๋™ํ•œ ํ›„ ์ž…๋ ฅํ•œ ์—ฐ๋ฝ์ฒ˜๋ฅผ ๋ชฉ๋ก์— ์ถ”๊ฐ€ํ•˜๋„๋ก ๊ตฌํ˜„ - ๊ฐ ํ•„๋“œ์— ์ž…๋ ฅ๋œ ๋ฐ์ดํ„ฐ๋ฅผ save ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅผ ๋•Œ ๊ฒ€์ฆํ•˜๋„๋ก ๊ตฌํ˜„ - ์ด๋ฆ„์€ ์‚ฌ์šฉ์ž๊ฐ€ ์ค‘๊ฐ„์— ๋„์–ด์“ฐ๊ธฐ๋ฅผ ํ•˜๋”๋ผ๋„ ๋„์–ด์“ฐ๊ธฐ๋ฅผ ์ œ๊ฑฐ - ๋‚˜์ด๋Š” ์ˆซ์ž๋กœ๋งŒ ์ž…๋ ฅ, ์„ธ ์ž๋ฆฌ์ˆ˜ ์ดํ•˜ - ์—ฐ๋ฝ์ฒ˜๋Š” ์ค‘๊ฐ„์— -๋กœ ๊ตฌ๋ถ„, -๋Š” ๋‘ ๊ฐœ ์กด์žฌ, -์„ ์ œ์™ธํ•˜๊ณ  ์ˆซ์ž๋Š” 9์ž๋ฆฌ ์ด์ƒ ์ƒˆ ์—ฐ๋ฝ์ฒ˜๋ฅผ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ๋Š” ํ™”๋ฉด์„ ๊ตฌ์„ฑํ•˜๊ณ  ์—ฐ๋ฝ์ฒ˜๋ฅผ ์‹ค์ œ๋กœ ๋ชฉ๋ก์— ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๋Š” ๋‹จ๊ณ„์ด๋‹ค. ํ”„๋กœ์ ํŠธ ์š”๊ตฌ์‚ฌํ•ญ์„ ์‚ดํŽด๋ณธ ํ›„ Present Modal ๋ฐฉ์‹์œผ๋กœ ํ™”๋ฉด์„ ๋„์šฐ๊ธฐ๋กœ ๊ฒฐ์ •ํ•˜์˜€๊ณ , ์ฝ”๋“œ๋ฅผ ํ†ตํ•˜์—ฌ ๊ตฌํ˜„ํ•˜์˜€๋‹ค. data๋Š” ์‹ฑ๊ธ€ํ†ค์„ ์ฑ„ํƒํ•˜์ง€ ์•Š๊ณ  ์žˆ์–ด์„œ delegate pattern์„ ์ฑ„ํƒํ•˜์—ฌ `SendContactDataDelegate`๋ฅผ ํ”„๋กœํ† ์ฝœ๋กœ ๊ตฌํ˜„, ์—ฐ๋ฝ์ฒ˜ ๋ชฉ๋ก ๋ทฐ์—์„œ extension์œผ๋กœ ํ•จ์ˆ˜๋ฅผ ๊ตฌํ˜„ํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฅผ ์ „๋‹ฌ๋ฐ›๊ณ  ํ•ด๋‹น ๋ฐ์ดํ„ฐ๊ฐ€ ๋ฐ˜์˜๋˜๋„๋ก table view๋ฅผ reloadํ•˜๋„๋ก ๊ตฌํ˜„ํ•˜์˜€๋‹ค. --- ## **๐ŸŽฏ ํŠธ๋Ÿฌ๋ธ” ์ŠˆํŒ…** ### 1๏ธโƒฃ API Design GuideLine์— ๋งž์ถ˜ Refactoring ์–ด๋–ป๊ฒŒ ๋ณด๋ฉด ๊ธฐ๋Šฅ์— ๋Œ€ํ•œ ๋ฌธ์ œ์ ์€ ์•„๋‹ˆ์˜€์ง€๋งŒ ๊ตฌํ˜„ ๊ณผ์ •์—์„œ ๋ถˆํŽธํ•จ์ด ์žˆ์–ด ์œ„ ํ•ญ๋ชฉ์—์„œ ๋‹ค๋ฃจ์—ˆ๋‹ค. ์ด์ „ ํ”„๋กœ์ ํŠธ๋ฅผ ์ง„ํ–‰ํ•˜๋ฉด์„œ API Design Guide๋ฅผ ์ตœ๋Œ€ํ•œ ๋”ฐ๋ผ๊ฐ€๋ณด๋ ค ํ•˜์˜€์ง€๋งŒ ์ต์ˆ™ํ•˜์ง€ ์•Š๋‹ค๋ณด๋‹ˆ ์–ด๋ ค์›€์ด ๋ฐœ์ƒํ•˜์˜€๊ณ  ์•„๋ž˜์˜ ์ฝ”๋“œ๋ฅผ ์˜ˆ์‹œ๋กœ ์ข€ ๋” ๋ฌธ์žฅ์ฒ˜๋Ÿผ ์ž˜ ์ฝํžˆ๋„๋ก ๋ฆฌํŽ™ํ† ๋งํ•˜์˜€๋‹ค. ```Swift // โ›”๏ธ class Converter { func convertToCharacter(this sentence: String) -> [Character] { var characterArray = [Character]() for index in sentence { characterArray.append(index) } return characterArray } func convertToString(_ word: [Character]) -> String { return String(word) } } ``` ```Swift // โœ… class Converter { func renderToCharacter(_ sentence: String) -> [Character] { var characters = [Character]() for character in sentence { characters.append(character) } return characters } func renderToString(_ characters: [Character]) -> String { return String(characters) } } ``` ### 2๏ธโƒฃ ReusableTableViewCell ํ”„๋กœํ† ์ฝœ์„ ์ฑ„ํƒํ•œ TableViewCell Step1~2๋ฅผ ์ง„ํ–‰ํ•˜๋ฉด์„œ Review๋ฅผ ๋ฐ›์€ ๋‚ด์šฉ์—์„œ ์•„๋ž˜์™€ ๊ฐ™์€ ์˜๊ฒฌ์„ ๋ฐ›๊ฒŒ๋˜์–ด ๋ฆฌํŽ™ํ† ๋ง์„ ํ•˜๊ฒŒ ๋˜์—ˆ๋‹ค. prototype cell์„ ํ™œ์šฉํ•˜๋ฉด์„œ `cellIdentifier`๋ฅผ ์ง์ ‘ ์ง€์ •ํ•ด์ฃผ๊ณ , ์ด๋ฅผ ๋งค๊ฐœ๋ณ€์ˆ˜์— ์ง์ ‘ ํ• ๋‹นํ•˜์—ฌ `dequeueReusableCell` ํ˜ธ์ถœ ์‹œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜์˜€์œผ๋‚˜, PR review ์ง„ํ–‰ ์ค‘์— ๋ฆฌ๋ทฐ์–ด์˜ ์ถ”์ฒœ์œผ๋กœ Protocol & Generic์„ ์‚ฌ์šฉํ•˜์—ฌ ํœด๋จผ ์—๋Ÿฌ์˜ ๋ฐœ์ƒ์„ ์ค„์ผ ์ˆ˜ ์žˆ๋„๋ก ๊ฐœ์„ ํ•ด ๋ณด์•˜๋‹ค. > `cellIdentifier`๋Š” ๊ฒฐ๊ตญ TableViewCell์˜ ์ด๋ฆ„๊ณผ ๋™์ผํ•  ๊ฒƒ์ธ๋ฐ ๋งค๋ฒˆ ์ €๋ ‡๊ฒŒ ์ง์ ‘ ์ž‘์„ฑํ•˜๊ฒŒ๋˜๋ฉด, ํœด๋จผ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•  ํ™•๋ฅ ๋„ ์žˆ๊ณ  ์–ด๋Аํ•œ์ชฝ์ด ๋ณ€๊ฒฝ๋˜์—ˆ์„๋•Œ ๋‹ค๋ฅธ์ชฝ๋„ ์ด๋ฆ„์„ ๊ฐ™์ด ๋ณ€๊ฒฝํ•ด์ฃผ์–ด์•ผํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ด๋ฅผ ์ œ๋„ค๋ฆญ๊ณผ protocol์„ ์‚ฌ์šฉํ•ด์„œ ๊ฐ„ํŽธํ•˜๊ฒŒ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•์ด ์žˆ์œผ๋‹ˆ ํ•œ ๋ฒˆ ์ฐธ๊ณ ํ•ด๋ณด์‹œ๋ฉด ์ข‹์„ ๊ฒƒ ๊ฐ™๋„ค์š”. ```Swift // โ›”๏ธ func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cellIdentifier = "infoCell" let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) var infoContent = cell.defaultContentConfiguration() infoContent.text = "Name(age)" infoContent.secondaryText = "contact-number" cell.contentConfiguration = infoContent return cell } ``` ```Swift // โœ… // ๊ตฌํ˜„๋ถ€ // Protocol์„ ์ฑ„ํƒํ•˜์—ฌ ํ•„์š”ํ•œ Cell Type๋งŒ ์‚ฌ์šฉ extension UITableViewCell: ReusableTableViewCell {} // UITableView๋ฅผ ํ™•์žฅํ•˜์—ฌ Custom Method ๊ตฌํ˜„ extension UITableView { func dequeueReusableCell<T: UITableViewCell>(cellClass: T.Type, for indexPath: IndexPath) -> T { guard let cell = dequeueReusableCell(withIdentifier: T.reuseIdentifier, for: indexPath) as? T else { fatalError("Unable Dequeue Reusable") } return cell } } // ํ˜ธ์ถœ๋ถ€ override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(cellClass: UITableViewCell.self, for: indexPath) cell.configurationUpdateHandler = { cell, state in var infoContent = cell.defaultContentConfiguration().updated(for: state) infoContent.text = "\(self.contactInfomation[indexPath.row].name)(\(self.contactInfomation[indexPath.row].age))" infoContent.secondaryText = self.contactInfomation[indexPath.row].phoneNumber cell.accessoryType = .disclosureIndicator cell.contentConfiguration = infoContent } return cell } ``` 1. ํ•ด๋‹น prototype cell์ด `UITableViewCell`์„ ์ƒ์†๋ฐ›๊ฒŒ ํ•œ๋‹ค. 2. prototype cell์˜ identifier๋ฅผ `UITableViewCell`๋กœ ์ˆ˜์ •ํ•œ๋‹ค. ์œ„์˜ ๊ณผ์ •์œผ๋กœ ์ธํ•ด ๋ฆฌ๋ทฐ์–ด๊ฐ€ ์ œ์•ˆํ•œ ํœด๋จผ ์—๋Ÿฌ์˜ ๋ฐœ์ƒ ๊ฐ€๋Šฅ์„ฑ์€ ์ค„์–ด๋“ค์—ˆ์œผ๋‚˜, ๋งค๋ฒˆ cell์˜ ํƒ€์ž…์„ ์ง€์ •ํ•ด์ฃผ๋Š” ์ž‘์—…์ด ๋ถˆํ•„์š”ํ•˜๋‹ค๊ณ  ์ƒ๊ฐ๋˜์–ด ๋ฉ”ํƒ€ ํƒ€์ž…์„ ํ™œ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์œผ๋กœ ๋‹ค์‹œ ํ•œ๋ฒˆ ๋ฆฌํŒฉํ† ๋ง์„ ์ง„ํ–‰ํ•˜์˜€๋‹ค. ๋ฉ”ํƒ€ ํƒ€์ž…์„ ํ™œ์šฉํ•˜์—ฌ ๊ฐœ์„ ๋œ ์ฝ”๋“œ๋Š” `dequeueReusableCell`๋ฅผ ํ˜ธ์ถœํ•  ๋•Œ ํ•ด๋‹น ์…€์˜ ํด๋ž˜์Šค์˜ ๋ฉ”ํƒ€ํƒ€์ž…์„ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ์ „๋‹ฌํ•˜๋ฉด ์…€์„ ์ƒ์„ฑํ•  ๋•Œ ์…€์˜ ํƒ€์ž…์„ ์ง€์ •ํ•ด ์ฃผ์–ด์•ผ ํ•  ํ•„์š”๊ฐ€ ์—†์œผ๋ฉฐ ์•Œ์•„์„œ ํƒ€์ž…์ถ”๋ก ์„ ํ•˜๊ณ , ๊ทธ์— ๋งž๋Š” `reuseIdentifier`๋ฅผ ์ •ํ™•ํ•˜๊ฒŒ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ๊ฒŒ ๋˜์—ˆ๋‹ค. --- ## ๐Ÿ’ป **์‹คํ–‰ ํ™”๋ฉด** <img src="https://user-images.githubusercontent.com/71758542/217981706-6b88c8f3-d8bc-461a-8125-4cb0363cbb56.gif">