diff --git a/ios-contact-manager-ui/.DS_Store b/ios-contact-manager-ui/.DS_Store
index a49c6b2d..38153090 100644
Binary files a/ios-contact-manager-ui/.DS_Store and b/ios-contact-manager-ui/.DS_Store differ
diff --git a/ios-contact-manager-ui/ContactManagerUI/AddContactViewController.swift b/ios-contact-manager-ui/ContactManagerUI/AddContactViewController.swift
new file mode 100644
index 00000000..68669687
--- /dev/null
+++ b/ios-contact-manager-ui/ContactManagerUI/AddContactViewController.swift
@@ -0,0 +1,86 @@
+//
+// AddContactViewController.swift
+// ContactManagerUI
+//
+// Created by sei_dev on 2/6/23.
+//
+
+import UIKit
+
+final class AddContactViewController: UIViewController {
+ @IBOutlet weak var contactTextField: UITextField!
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+ contactTextField.delegate = self
+ }
+}
+
+extension AddContactViewController: UITextFieldDelegate {
+ func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
+ if let oldText = textField.text, let textRange = Range(range, in: oldText) {
+ // newText == 현재 text field에 있는 텍스트. 아직 textField에는 업데이트 되지 않은 상태
+ let newText = oldText.replacingCharacters(in: textRange, with: string)
+
+ // range 정보를 갖고 parse한 결과를 textField의 text에 할당
+ textField.text = parse(newText, using: range)
+ }
+ return false
+ }
+
+ private func parse(_ newText: String, using range: NSRange) -> String {
+ /*
+ range.location == newText.count
+ range.length == 삭제된 character 수
+ */
+ switch (range.location, range.length) {
+ case (12, 1...), (11, 0): // 2-4-4로 바뀌어야 하는 경우
+ return relocateHyphen(of: newText, locationsOfHyphen: [2, 7])
+ case (11, 1...): // 2-3-4로 바뀌어야 하는 경우
+ return relocateHyphen(of: newText, locationsOfHyphen: [2, 6])
+ case (12, 0): // 3-4-4로 바뀌어야 하는 경우
+ return relocateHyphen(of: newText, locationsOfHyphen: [3, 8])
+ case (3, 1...), (7, 1...): // 맨 뒤 -를 없애는 경우
+ return removeLastHyphen(from: newText)
+ case (2, _), (6, _): // -를 추가해야 하는 경우
+ return newText.inserting(Character.hyphen, at: range.location)
+ default: // 그 외의 경우
+ return newText
+ }
+ }
+
+ private func removeLastHyphen(from text: String) -> String {
+ if text.last == Character.hyphen {
+ return String(text.dropLast())
+ }
+ return text
+ }
+
+ private func relocateHyphen(of string: String, locationsOfHyphen indices: [Int]) -> String {
+ var relocated = string.replacingOccurrences(of: String.hyphen, with: String.empty)
+ for index in indices {
+ relocated.insert(Character.hyphen, at: index)
+ }
+ return relocated
+ }
+}
+
+
+extension String {
+ static var hyphen: String { "-" }
+ static var empty: String { "" }
+
+ mutating func insert(_ newElement: Character, at i: Int) {
+ self.insert(newElement, at: self.index(self.startIndex, offsetBy: i))
+ }
+
+ func inserting(_ newElement: Character, at i: Int) -> String {
+ var newString = self
+ newString.insert(newElement, at: self.index(self.startIndex, offsetBy: i))
+ return newString
+ }
+}
+
+extension Character {
+ static var hyphen: Character { "-" }
+}
diff --git a/ios-contact-manager-ui/ContactManagerUI/Base.lproj/Main.storyboard b/ios-contact-manager-ui/ContactManagerUI/Base.lproj/Main.storyboard
deleted file mode 100644
index c75716d6..00000000
--- a/ios-contact-manager-ui/ContactManagerUI/Base.lproj/Main.storyboard
+++ /dev/null
@@ -1,57 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/ios-contact-manager-ui/ContactManagerUI/AppDelegate.swift b/ios-contact-manager-ui/ContactManagerUI/Common/AppDelegate.swift
similarity index 100%
rename from ios-contact-manager-ui/ContactManagerUI/AppDelegate.swift
rename to ios-contact-manager-ui/ContactManagerUI/Common/AppDelegate.swift
diff --git a/ios-contact-manager-ui/ContactManagerUI/SceneDelegate.swift b/ios-contact-manager-ui/ContactManagerUI/Common/SceneDelegate.swift
similarity index 98%
rename from ios-contact-manager-ui/ContactManagerUI/SceneDelegate.swift
rename to ios-contact-manager-ui/ContactManagerUI/Common/SceneDelegate.swift
index dd1e5902..251e2758 100644
--- a/ios-contact-manager-ui/ContactManagerUI/SceneDelegate.swift
+++ b/ios-contact-manager-ui/ContactManagerUI/Common/SceneDelegate.swift
@@ -32,6 +32,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
}
func sceneWillResignActive(_ scene: UIScene) {
+ ContactsController.shared.save()
// Called when the scene will move from an active state to an inactive state.
// This may occur due to temporary interruptions (ex. an incoming phone call).
}
diff --git a/ios-contact-manager-ui/ContactManagerUI/ContactError.swift b/ios-contact-manager-ui/ContactManagerUI/ContactError.swift
deleted file mode 100644
index 7f43e09c..00000000
--- a/ios-contact-manager-ui/ContactManagerUI/ContactError.swift
+++ /dev/null
@@ -1,14 +0,0 @@
-//
-// ContactError.swift
-// ContactManagerUI
-//
-// Created by sei_dev on 2/1/23.
-//
-
-import Foundation
-
-enum ContactError: Error {
- case FileNotParse
- case FileNotFound
- case FileNotLoad
-}
diff --git a/ios-contact-manager-ui/ContactManagerUI/ContactViewController.swift b/ios-contact-manager-ui/ContactManagerUI/ContactViewController.swift
deleted file mode 100644
index 9eb57692..00000000
--- a/ios-contact-manager-ui/ContactManagerUI/ContactViewController.swift
+++ /dev/null
@@ -1,28 +0,0 @@
-//
-// ViewController.swift
-// ContactManagerUI
-//
-// Created by 송선진 on 2023/01/30.
-//
-
-import UIKit
-
-final class ContactViewController: UIViewController {
-
- @IBOutlet private weak var tableView: UITableView!
- private let dataSource = TableViewDataSource()
-
- override func viewDidLoad() {
- super.viewDidLoad()
- setupTableView()
- }
-
- private func setupTableView() {
- tableView.delegate = self
- tableView.dataSource = dataSource
- }
-}
-
-extension ContactViewController: UITableViewDelegate {
-
-}
diff --git a/ios-contact-manager-ui/ContactManagerUI/Controllers/AddContactViewController.swift b/ios-contact-manager-ui/ContactManagerUI/Controllers/AddContactViewController.swift
new file mode 100644
index 00000000..ecb5c247
--- /dev/null
+++ b/ios-contact-manager-ui/ContactManagerUI/Controllers/AddContactViewController.swift
@@ -0,0 +1,117 @@
+//
+// AddContactViewController.swift
+// ContactManagerUI
+//
+// Created by sei_dev on 2/6/23.
+//
+
+import UIKit
+
+final class AddContactViewController: UIViewController {
+
+ @IBOutlet private weak var nameTextField: UITextField!
+ @IBOutlet private weak var ageTextField: UITextField!
+ @IBOutlet private weak var contactTextField: UITextField!
+
+ weak var newContactDelegate: NewContactDelegate?
+
+ private let contactTextHyphenPolicyLength = 10
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+ contactTextField.delegate = self
+ }
+
+ @IBAction private func saveButtonTapped(_ sender: Any) {
+ guard let name = nameTextField.text,
+ let age = ageTextField.text,
+ let contact = contactTextField.text else {
+ return
+ }
+
+ do {
+ let newContact = try UserInfo(name: name, age: age, phone: contact)
+ ContactsController.shared.add(user: newContact)
+ newContactDelegate?.addNewContact()
+ dismiss(animated: true)
+ } catch {
+ makeErrorAlert(description: error.localizedDescription)
+ }
+ }
+
+ @IBAction private func cancelButtonTapped(_ sender: Any) {
+ makeCancelAlert()
+ }
+}
+
+extension AddContactViewController {
+ private func makeErrorAlert(description: String) {
+ let alert = UIAlertController(
+ title: description,
+ message: nil,
+ preferredStyle: .alert
+ )
+ let checkAction = UIAlertAction(title: "확인", style: .default)
+ alert.addAction(checkAction)
+ present(alert, animated: true)
+ }
+
+ private func makeCancelAlert() {
+ let alert = UIAlertController(
+ title: "정말로 취소하시겠습니까?",
+ message: nil,
+ preferredStyle: .alert
+ )
+ let noAction = UIAlertAction(title: "아니오", style: .default)
+ let yesAction = UIAlertAction(title: "예", style: .destructive) { [weak self] _ in
+ self?.dismiss(animated: true)
+ }
+ alert.addAction(noAction)
+ alert.addAction(yesAction)
+ alert.preferredAction = yesAction
+ present(alert, animated: true)
+ }
+}
+
+extension AddContactViewController: UITextFieldDelegate {
+ func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
+ if let oldText = textField.text, let textRange = Range(range, in: oldText) {
+ let newText = oldText.replacingCharacters(in: textRange, with: string)
+ textField.text = parse(newText, using: range)
+ }
+ return false
+ }
+
+ private enum LocationsOfHyphen {
+ static let forTwoFourFour = [2, 7]
+ static let forTwoThreeFour = [2, 6]
+ static let forThreeFourFour = [3, 8]
+ }
+
+ private func parse(_ newText: String, using range: NSRange) -> String {
+ let digitString = newText.replacingOccurrences(of: String.hyphen, with: String.empty)
+ switch digitString.count {
+ case .. String {
+ if text.last == Character.hyphen {
+ return String(text.dropLast())
+ }
+ return text
+ }
+
+ private func relocateHyphen(of string: String, at indices: [Int]) -> String {
+ var relocated = string
+ for (index, stringIndex) in indices.enumerated() where stringIndex - index < string.count {
+ relocated.insert(Character.hyphen, at: stringIndex)
+ }
+ return relocated
+ }
+}
diff --git a/ios-contact-manager-ui/ContactManagerUI/Controllers/ContactViewController.swift b/ios-contact-manager-ui/ContactManagerUI/Controllers/ContactViewController.swift
new file mode 100644
index 00000000..f28bb8d8
--- /dev/null
+++ b/ios-contact-manager-ui/ContactManagerUI/Controllers/ContactViewController.swift
@@ -0,0 +1,78 @@
+//
+// ContactViewController.swift
+// ContactManagerUI
+//
+// Created by 송선진 on 2023/01/30.
+//
+
+import UIKit
+
+final class ContactViewController: UIViewController, UITableViewDelegate {
+ private enum Section {
+ case main
+ }
+
+ @IBOutlet private weak var tableView: UITableView!
+ private var dataSource: DataSource?
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+ setupTableView()
+ setupDataSource()
+ }
+
+ override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
+ guard let addViewController = segue.destination as? AddContactViewController else {
+ return
+ }
+ addViewController.newContactDelegate = self
+ }
+
+ private func setupTableView() {
+ tableView.dataSource = dataSource
+ tableView.register(ContactCell.self)
+ }
+}
+
+extension ContactViewController {
+ private func setupDataSource() {
+ dataSource = DataSource(tableView: tableView) { tableView, indexPath, itemIdentifier in
+ let cell = tableView.dequeue(ContactCell.self, cellForRowAt: indexPath)
+ cell.content = itemIdentifier
+ return cell
+ }
+ dataSource?.update(animatingDifferences: false)
+ }
+}
+
+extension ContactViewController {
+ private final class DataSource: UITableViewDiffableDataSource {
+ typealias Snapshot = NSDiffableDataSourceSnapshot
+
+ func update(animatingDifferences: Bool) {
+ var snapshot = Snapshot()
+ snapshot.appendSections([.main])
+ snapshot.appendItems(ContactsController.shared.contacts)
+ apply(snapshot, animatingDifferences: animatingDifferences)
+ }
+
+ override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
+ return true
+ }
+
+ override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
+ if editingStyle == .delete {
+ if itemIdentifier(for: indexPath) != nil {
+ ContactsController.shared.remove(index: indexPath.row)
+ update(animatingDifferences: true)
+ }
+ }
+ }
+ }
+}
+
+extension ContactViewController: NewContactDelegate {
+ func addNewContact() {
+ dataSource?.update(animatingDifferences: true)
+ }
+}
diff --git a/ios-contact-manager-ui/ContactManagerUI/Extension+UserInfo.swift b/ios-contact-manager-ui/ContactManagerUI/Extension+UserInfo.swift
deleted file mode 100644
index 7a60cc3c..00000000
--- a/ios-contact-manager-ui/ContactManagerUI/Extension+UserInfo.swift
+++ /dev/null
@@ -1,17 +0,0 @@
-//
-// Extension+UserInfo.swift
-// ContactManagerUI
-//
-// Created by 송선진 on 2023/02/01.
-//
-
-import Foundation
-
-extension UserInfo {
- var title: String {
- "\(self.name)(\(self.age))"
- }
- var subtitle: String {
- self.phone
- }
-}
diff --git a/ios-contact-manager-ui/ContactManagerUI/Extensions/Extension+Character.swift b/ios-contact-manager-ui/ContactManagerUI/Extensions/Extension+Character.swift
new file mode 100644
index 00000000..1c093f76
--- /dev/null
+++ b/ios-contact-manager-ui/ContactManagerUI/Extensions/Extension+Character.swift
@@ -0,0 +1,14 @@
+//
+// Extension+Character.swift
+// ContactManagerUI
+//
+// Created by sei_dev on 2/10/23.
+//
+
+import Foundation
+
+extension Character {
+ static var hyphen: Character {
+ return "-"
+ }
+}
diff --git a/ios-contact-manager-ui/ContactManagerUI/Extensions/Extension+String.swift b/ios-contact-manager-ui/ContactManagerUI/Extensions/Extension+String.swift
new file mode 100644
index 00000000..2356556d
--- /dev/null
+++ b/ios-contact-manager-ui/ContactManagerUI/Extensions/Extension+String.swift
@@ -0,0 +1,22 @@
+//
+// Extension+String.swift
+// ContactManagerUI
+//
+// Created by sei_dev on 2/10/23.
+//
+
+import Foundation
+
+extension String {
+ static var hyphen: String {
+ return "-"
+ }
+
+ static var empty: String {
+ return ""
+ }
+
+ mutating func insert(_ newElement: Character, at i: Int) {
+ insert(newElement, at: index(startIndex, offsetBy: i))
+ }
+}
diff --git a/ios-contact-manager-ui/ContactManagerUI/Extensions/Extension+TableView.swift b/ios-contact-manager-ui/ContactManagerUI/Extensions/Extension+TableView.swift
new file mode 100644
index 00000000..add8b6d6
--- /dev/null
+++ b/ios-contact-manager-ui/ContactManagerUI/Extensions/Extension+TableView.swift
@@ -0,0 +1,21 @@
+//
+// Extension+TableView.swift
+// ContactManagerUI
+//
+// Created by 송선진 on 2023/02/10.
+//
+
+import UIKit
+
+extension UITableView {
+ func register(_ Type: T.Type) {
+ register(Type, forCellReuseIdentifier: Type.reuseIdentifier)
+ }
+
+ func dequeue(_ t: T.Type, cellForRowAt indexPath: IndexPath) -> T {
+ guard let cell = dequeueReusableCell(withIdentifier: T.reuseIdentifier, for: indexPath) as? T else {
+ fatalError("\(T.self) is not registered!")
+ }
+ return cell
+ }
+}
diff --git a/ios-contact-manager-ui/ContactManagerUI/Extensions/Extension+TableViewCell.swift b/ios-contact-manager-ui/ContactManagerUI/Extensions/Extension+TableViewCell.swift
new file mode 100644
index 00000000..69452c34
--- /dev/null
+++ b/ios-contact-manager-ui/ContactManagerUI/Extensions/Extension+TableViewCell.swift
@@ -0,0 +1,10 @@
+//
+// Extension+TableViewCell.swift
+// ContactManagerUI
+//
+// Created by sei_dev on 2/10/23.
+//
+
+import UIKit
+
+extension UITableViewCell: Reusable { }
diff --git a/ios-contact-manager-ui/ContactManagerUI/Extensions/Extension+UserInfo.swift b/ios-contact-manager-ui/ContactManagerUI/Extensions/Extension+UserInfo.swift
new file mode 100644
index 00000000..33f9b592
--- /dev/null
+++ b/ios-contact-manager-ui/ContactManagerUI/Extensions/Extension+UserInfo.swift
@@ -0,0 +1,18 @@
+//
+// Extension+UserInfo.swift
+// ContactManagerUI
+//
+// Created by 송선진 on 2023/02/01.
+//
+
+import Foundation
+
+extension UserInfo: CellFormat {
+ var mainText: String {
+ return "\(name) (\(age))"
+ }
+
+ var subText: String {
+ return "\(phone)"
+ }
+}
diff --git a/ios-contact-manager-ui/ContactManagerUI/Model/ContactsController.swift b/ios-contact-manager-ui/ContactManagerUI/Model/ContactsController.swift
new file mode 100644
index 00000000..df51d730
--- /dev/null
+++ b/ios-contact-manager-ui/ContactManagerUI/Model/ContactsController.swift
@@ -0,0 +1,46 @@
+//
+// ContactsController.swift
+// ContactManagerUI
+//
+// Created by 송선진 on 2023/02/01.
+//
+
+import Foundation
+
+final class ContactsController: JSONCodable {
+ static let shared = ContactsController()
+ private init() { }
+
+ var fileName: String {
+ return "contacts.json"
+ }
+ private(set) lazy var contacts: [UserInfo] = decoding()
+}
+
+extension ContactsController {
+ private func decoding() -> [UserInfo] {
+ do {
+ return try decoder()
+ } catch {
+ print(error.localizedDescription)
+ return []
+ }
+ }
+
+ func save() {
+ do {
+ try encoder(data: contacts)
+ } catch {
+ print(error.localizedDescription)
+ }
+ }
+
+ func add(user: UserInfo) {
+ contacts.append(user)
+ }
+
+ func remove(index: Int) {
+ contacts.remove(at: index)
+ }
+}
+
diff --git a/ios-contact-manager-ui/ContactManagerUI/ModelData.swift b/ios-contact-manager-ui/ContactManagerUI/ModelData.swift
deleted file mode 100644
index 713d7759..00000000
--- a/ios-contact-manager-ui/ContactManagerUI/ModelData.swift
+++ /dev/null
@@ -1,35 +0,0 @@
-//
-// ModelData.swift
-// ContactManagerUI
-//
-// Created by 송선진 on 2023/02/01.
-//
-
-import Foundation
-
-final class ModelData {
- // TODO: Error 처리하기
- private(set) var contacts: [UserInfo] = (try? load("contacts.json")) ?? []
-}
-
-func load(_ fileName: String) throws -> T {
- let data: Data
-
- guard let file = Bundle.main.url(forResource: fileName, withExtension: nil) else {
- throw ContactError.FileNotFound
- }
-
- do {
- data = try Data(contentsOf: file)
- } catch {
- throw ContactError.FileNotLoad
- }
-
- do {
- let decoder = JSONDecoder()
- return try decoder.decode(T.self, from: data)
- } catch {
- throw ContactError.FileNotParse
- }
-}
-
diff --git a/ios-contact-manager-ui/ContactManagerUI/Protocols/CellFormat.swift b/ios-contact-manager-ui/ContactManagerUI/Protocols/CellFormat.swift
new file mode 100644
index 00000000..13c19b2b
--- /dev/null
+++ b/ios-contact-manager-ui/ContactManagerUI/Protocols/CellFormat.swift
@@ -0,0 +1,13 @@
+//
+// CellFormat.swift
+// ContactManagerUI
+//
+// Created by sei_dev on 2/10/23.
+//
+
+import Foundation
+
+protocol CellFormat {
+ var mainText: String { get }
+ var subText: String { get }
+}
diff --git a/ios-contact-manager-ui/ContactManagerUI/Protocols/JSONCodable.swift b/ios-contact-manager-ui/ContactManagerUI/Protocols/JSONCodable.swift
new file mode 100644
index 00000000..c7ee8315
--- /dev/null
+++ b/ios-contact-manager-ui/ContactManagerUI/Protocols/JSONCodable.swift
@@ -0,0 +1,48 @@
+//
+// JSONCodable.swift
+// ContactManagerUI
+//
+// Created by 송선진 on 2/10/23.
+//
+
+import Foundation
+
+enum FileError: Error, LocalizedError {
+ case notFound(fileName: String)
+
+ var errorDescription: String? {
+ switch self {
+ case .notFound(let fileName):
+ return "\(fileName) 파일이 존재하지 않습니다."
+ }
+ }
+}
+
+protocol JSONCodable {
+ var fileName: String { get }
+ func fileURL() throws -> URL
+ func encoder(data newData: E) throws
+ func decoder() throws -> D
+}
+
+extension JSONCodable {
+ func fileURL() throws -> URL {
+ guard let url = Bundle.main.url(forResource: fileName, withExtension: nil) else {
+ throw FileError.notFound(fileName: fileName)
+ }
+ return url
+ }
+
+ func encoder(data newData: E) throws {
+ let encoder = JSONEncoder()
+ encoder.outputFormatting = .prettyPrinted
+ let updatedData = try encoder.encode(newData)
+ try updatedData.write(to: fileURL())
+ }
+
+ func decoder() throws -> D {
+ let decoder = JSONDecoder()
+ let data = try Data(contentsOf: fileURL())
+ return try decoder.decode(D.self, from: data)
+ }
+}
diff --git a/ios-contact-manager-ui/ContactManagerUI/Protocols/NewContactDelegate.swift b/ios-contact-manager-ui/ContactManagerUI/Protocols/NewContactDelegate.swift
new file mode 100644
index 00000000..b9113d74
--- /dev/null
+++ b/ios-contact-manager-ui/ContactManagerUI/Protocols/NewContactDelegate.swift
@@ -0,0 +1,12 @@
+//
+// NewContactDelegate.swift
+// ContactManagerUI
+//
+// Created by 송선진 on 2023/02/10.
+//
+
+import Foundation
+
+protocol NewContactDelegate: AnyObject {
+ func addNewContact()
+}
diff --git a/ios-contact-manager-ui/ContactManagerUI/Protocols/Reusable.swift b/ios-contact-manager-ui/ContactManagerUI/Protocols/Reusable.swift
new file mode 100644
index 00000000..73c9c03d
--- /dev/null
+++ b/ios-contact-manager-ui/ContactManagerUI/Protocols/Reusable.swift
@@ -0,0 +1,18 @@
+//
+// Reusable.swift
+// ContactManagerUI
+//
+// Created by sei_dev on 2/10/23.
+//
+
+import Foundation
+
+protocol Reusable {
+ static var reuseIdentifier: String { get }
+}
+
+extension Reusable {
+ static var reuseIdentifier: String {
+ String(describing: self)
+ }
+}
diff --git a/ios-contact-manager-ui/ContactManagerUI/Resources/contacts.json b/ios-contact-manager-ui/ContactManagerUI/Resources/contacts.json
index 6669a48c..e8940674 100644
--- a/ios-contact-manager-ui/ContactManagerUI/Resources/contacts.json
+++ b/ios-contact-manager-ui/ContactManagerUI/Resources/contacts.json
@@ -8,61 +8,6 @@
"name": "james2",
"age": 35,
"phone": "05-323-2234"
- },
- {
- "name": "james3",
- "age": 45,
- "phone": "05-323-2234"
- },
- {
- "name": "james4",
- "age": 35,
- "phone": "05-323-2234"
- },
- {
- "name": "james5",
- "age": 35,
- "phone": "05-323-2234"
- },
- {
- "name": "james6",
- "age": 30,
- "phone": "05-323-2234"
- },
- {
- "name": "james7",
- "age": 5,
- "phone": "05-323-2234"
- },
- {
- "name": "james8",
- "age": 8,
- "phone": "05-323-2234"
- },
- {
- "name": "james9",
- "age": 13,
- "phone": "05-323-2234"
- },
- {
- "name": "james10",
- "age": 16,
- "phone": "05-323-2234"
- },
- {
- "name": "james11",
- "age": 22,
- "phone": "05-323-2234"
- },
- {
- "name": "james12",
- "age": 33,
- "phone": "05-323-2234"
- },
- {
- "name": "james13",
- "age": 57,
- "phone": "05-323-2234"
}
]
diff --git a/ios-contact-manager-ui/ContactManagerUI/TableViewDataSource.swift b/ios-contact-manager-ui/ContactManagerUI/TableViewDataSource.swift
index 0ea02e90..9c00aec0 100644
--- a/ios-contact-manager-ui/ContactManagerUI/TableViewDataSource.swift
+++ b/ios-contact-manager-ui/ContactManagerUI/TableViewDataSource.swift
@@ -8,9 +8,10 @@
import UIKit
final class TableViewDataSource: NSObject, UITableViewDataSource {
- private let contactsData = ModelData().contacts
+ private let contactsData = ModelData().load()
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+
return contactsData.count
}
diff --git a/ios-contact-manager-ui/ContactManagerUI/Base.lproj/LaunchScreen.storyboard b/ios-contact-manager-ui/ContactManagerUI/Views/Base.lproj/LaunchScreen.storyboard
similarity index 100%
rename from ios-contact-manager-ui/ContactManagerUI/Base.lproj/LaunchScreen.storyboard
rename to ios-contact-manager-ui/ContactManagerUI/Views/Base.lproj/LaunchScreen.storyboard
diff --git a/ios-contact-manager-ui/ContactManagerUI/Views/Base.lproj/Main.storyboard b/ios-contact-manager-ui/ContactManagerUI/Views/Base.lproj/Main.storyboard
new file mode 100644
index 00000000..eb9cbffa
--- /dev/null
+++ b/ios-contact-manager-ui/ContactManagerUI/Views/Base.lproj/Main.storyboard
@@ -0,0 +1,180 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ios-contact-manager-ui/ContactManagerUI/Views/ContactCell.swift b/ios-contact-manager-ui/ContactManagerUI/Views/ContactCell.swift
new file mode 100644
index 00000000..0fdc5d0a
--- /dev/null
+++ b/ios-contact-manager-ui/ContactManagerUI/Views/ContactCell.swift
@@ -0,0 +1,22 @@
+//
+// ContactCell.swift
+// ContactManagerUI
+//
+// Created by 송선진 on 2023/02/10.
+//
+
+import UIKit
+
+class ContactCell: UITableViewCell {
+
+ var content: CellFormat?
+
+ override func updateConfiguration(using state: UICellConfigurationState) {
+ var configuration = defaultContentConfiguration().updated(for: state)
+ configuration.text = content?.mainText
+ configuration.textProperties.font = .preferredFont(forTextStyle: .headline)
+ configuration.secondaryText = content?.subText
+ configuration.secondaryTextProperties.font = .preferredFont(forTextStyle: .body)
+ contentConfiguration = configuration
+ }
+}
diff --git a/ios-contact-manager-ui/ios-contact-manager-ui.xcodeproj/project.pbxproj b/ios-contact-manager-ui/ios-contact-manager-ui.xcodeproj/project.pbxproj
index ca97acec..ccad8a00 100644
--- a/ios-contact-manager-ui/ios-contact-manager-ui.xcodeproj/project.pbxproj
+++ b/ios-contact-manager-ui/ios-contact-manager-ui.xcodeproj/project.pbxproj
@@ -7,13 +7,21 @@
objects = {
/* Begin PBXBuildFile section */
- 4000CC39298A50F3003C8B95 /* TableViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4000CC38298A50F3003C8B95 /* TableViewDataSource.swift */; };
- 4000CC3B298A59DC003C8B95 /* ContactError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4000CC3A298A59DC003C8B95 /* ContactError.swift */; };
+ 4000CC3B298A59DC003C8B95 /* JSONCodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4000CC3A298A59DC003C8B95 /* JSONCodable.swift */; };
4062671E2954226900A2CBE4 /* UserInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4062671D2954226900A2CBE4 /* UserInfo.swift */; };
+ 40B17FDB299609A300307F06 /* CellFormat.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40B17FDA299609A300307F06 /* CellFormat.swift */; };
+ 40B17FDF29960E9400307F06 /* Reusable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40B17FDE29960E9400307F06 /* Reusable.swift */; };
+ 40B17FE129960FE000307F06 /* Extension+TableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40B17FE029960FE000307F06 /* Extension+TableViewCell.swift */; };
+ 40BC412D2990BB9A00BF62B3 /* AddContactViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40BC412C2990BB9A00BF62B3 /* AddContactViewController.swift */; };
+ 40F251B92996124100C7C5DA /* Extension+String.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40F251B82996124100C7C5DA /* Extension+String.swift */; };
+ 40F251BB2996125600C7C5DA /* Extension+Character.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40F251BA2996125600C7C5DA /* Extension+Character.swift */; };
40F965542952ED4200ACCA0C /* StringLiteral.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40F965532952ED4200ACCA0C /* StringLiteral.swift */; };
+ 4F6D4AAA2995E471001D2E8E /* ContactCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F6D4AA92995E471001D2E8E /* ContactCell.swift */; };
+ 4F6D4AAC299606CF001D2E8E /* Extension+TableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F6D4AAB299606CF001D2E8E /* Extension+TableView.swift */; };
+ 4F6D4AAE29960CE6001D2E8E /* NewContactDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F6D4AAD29960CE6001D2E8E /* NewContactDelegate.swift */; };
4FAE8E88298A4E60006DCD46 /* Extension+UserInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FAE8E87298A4E60006DCD46 /* Extension+UserInfo.swift */; };
4FAE8E8A298A4F79006DCD46 /* contacts.json in Resources */ = {isa = PBXBuildFile; fileRef = 4FAE8E89298A4F79006DCD46 /* contacts.json */; };
- 4FAE8E8D298A526F006DCD46 /* ModelData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FAE8E8C298A526F006DCD46 /* ModelData.swift */; };
+ 4FAE8E8D298A526F006DCD46 /* ContactsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FAE8E8C298A526F006DCD46 /* ContactsController.swift */; };
4FCF4FFD2952A4AE00CA262F /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FCF4FFC2952A4AE00CA262F /* main.swift */; };
4FCF50082953F79200CA262F /* IOManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FCF50072953F79200CA262F /* IOManager.swift */; };
4FCF500A2953F7B200CA262F /* Error.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FCF50092953F7B200CA262F /* Error.swift */; };
@@ -48,13 +56,21 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
- 4000CC38298A50F3003C8B95 /* TableViewDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableViewDataSource.swift; sourceTree = ""; };
- 4000CC3A298A59DC003C8B95 /* ContactError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactError.swift; sourceTree = ""; };
+ 4000CC3A298A59DC003C8B95 /* JSONCodable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSONCodable.swift; sourceTree = ""; };
4062671D2954226900A2CBE4 /* UserInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserInfo.swift; sourceTree = ""; };
+ 40B17FDA299609A300307F06 /* CellFormat.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CellFormat.swift; sourceTree = ""; };
+ 40B17FDE29960E9400307F06 /* Reusable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Reusable.swift; sourceTree = ""; };
+ 40B17FE029960FE000307F06 /* Extension+TableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Extension+TableViewCell.swift"; sourceTree = ""; };
+ 40BC412C2990BB9A00BF62B3 /* AddContactViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddContactViewController.swift; sourceTree = ""; };
+ 40F251B82996124100C7C5DA /* Extension+String.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Extension+String.swift"; sourceTree = ""; };
+ 40F251BA2996125600C7C5DA /* Extension+Character.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Extension+Character.swift"; sourceTree = ""; };
40F965532952ED4200ACCA0C /* StringLiteral.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringLiteral.swift; sourceTree = ""; };
+ 4F6D4AA92995E471001D2E8E /* ContactCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactCell.swift; sourceTree = ""; };
+ 4F6D4AAB299606CF001D2E8E /* Extension+TableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Extension+TableView.swift"; sourceTree = ""; };
+ 4F6D4AAD29960CE6001D2E8E /* NewContactDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewContactDelegate.swift; sourceTree = ""; };
4FAE8E87298A4E60006DCD46 /* Extension+UserInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Extension+UserInfo.swift"; sourceTree = ""; };
4FAE8E89298A4F79006DCD46 /* contacts.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = contacts.json; sourceTree = ""; };
- 4FAE8E8C298A526F006DCD46 /* ModelData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModelData.swift; sourceTree = ""; };
+ 4FAE8E8C298A526F006DCD46 /* ContactsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactsController.swift; sourceTree = ""; };
4FCF4FF92952A4AE00CA262F /* ios-contact-manager-ui */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = "ios-contact-manager-ui"; sourceTree = BUILT_PRODUCTS_DIR; };
4FCF4FFC2952A4AE00CA262F /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; };
4FCF50072953F79200CA262F /* IOManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IOManager.swift; sourceTree = ""; };
@@ -90,6 +106,65 @@
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
+ 40B17FD92996098C00307F06 /* Protocols */ = {
+ isa = PBXGroup;
+ children = (
+ 4000CC3A298A59DC003C8B95 /* JSONCodable.swift */,
+ 40B17FDA299609A300307F06 /* CellFormat.swift */,
+ 4F6D4AAD29960CE6001D2E8E /* NewContactDelegate.swift */,
+ 40B17FDE29960E9400307F06 /* Reusable.swift */,
+ );
+ path = Protocols;
+ sourceTree = "";
+ };
+ 4F5075152992304100C0AC18 /* Controllers */ = {
+ isa = PBXGroup;
+ children = (
+ 40BC412C2990BB9A00BF62B3 /* AddContactViewController.swift */,
+ 4FEEF66529879DE000FF6A89 /* ContactViewController.swift */,
+ );
+ path = Controllers;
+ sourceTree = "";
+ };
+ 4F5075162992306B00C0AC18 /* Extensions */ = {
+ isa = PBXGroup;
+ children = (
+ 4FAE8E87298A4E60006DCD46 /* Extension+UserInfo.swift */,
+ 4F6D4AAB299606CF001D2E8E /* Extension+TableView.swift */,
+ 40B17FE029960FE000307F06 /* Extension+TableViewCell.swift */,
+ 40F251B82996124100C7C5DA /* Extension+String.swift */,
+ 40F251BA2996125600C7C5DA /* Extension+Character.swift */,
+ );
+ path = Extensions;
+ sourceTree = "";
+ };
+ 4F507517299230CA00C0AC18 /* Model */ = {
+ isa = PBXGroup;
+ children = (
+ 4FAE8E8C298A526F006DCD46 /* ContactsController.swift */,
+ );
+ path = Model;
+ sourceTree = "";
+ };
+ 4F507518299230E000C0AC18 /* Views */ = {
+ isa = PBXGroup;
+ children = (
+ 4FEEF66729879DE000FF6A89 /* Main.storyboard */,
+ 4FEEF66C29879DE200FF6A89 /* LaunchScreen.storyboard */,
+ 4F6D4AA92995E471001D2E8E /* ContactCell.swift */,
+ );
+ path = Views;
+ sourceTree = "";
+ };
+ 4F5075192992318900C0AC18 /* Common */ = {
+ isa = PBXGroup;
+ children = (
+ 4FEEF66129879DE000FF6A89 /* AppDelegate.swift */,
+ 4FEEF66329879DE000FF6A89 /* SceneDelegate.swift */,
+ );
+ path = Common;
+ sourceTree = "";
+ };
4FAE8E8B298A4F90006DCD46 /* Resources */ = {
isa = PBXGroup;
children = (
@@ -135,18 +210,15 @@
4FEEF66029879DE000FF6A89 /* ContactManagerUI */ = {
isa = PBXGroup;
children = (
- 4FEEF66129879DE000FF6A89 /* AppDelegate.swift */,
- 4FEEF66329879DE000FF6A89 /* SceneDelegate.swift */,
- 4FEEF66529879DE000FF6A89 /* ContactViewController.swift */,
- 4000CC38298A50F3003C8B95 /* TableViewDataSource.swift */,
- 4FAE8E8C298A526F006DCD46 /* ModelData.swift */,
+ 4F5075192992318900C0AC18 /* Common */,
+ 4F5075152992304100C0AC18 /* Controllers */,
+ 4F507517299230CA00C0AC18 /* Model */,
+ 40B17FD92996098C00307F06 /* Protocols */,
+ 4F5075162992306B00C0AC18 /* Extensions */,
4FAE8E8B298A4F90006DCD46 /* Resources */,
- 4FAE8E87298A4E60006DCD46 /* Extension+UserInfo.swift */,
- 4FEEF66729879DE000FF6A89 /* Main.storyboard */,
+ 4F507518299230E000C0AC18 /* Views */,
4FEEF66A29879DE200FF6A89 /* Assets.xcassets */,
- 4FEEF66C29879DE200FF6A89 /* LaunchScreen.storyboard */,
4FEEF66F29879DE200FF6A89 /* Info.plist */,
- 4000CC3A298A59DC003C8B95 /* ContactError.swift */,
);
path = ContactManagerUI;
sourceTree = "";
@@ -259,20 +331,28 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
+ 40BC412D2990BB9A00BF62B3 /* AddContactViewController.swift in Sources */,
4FEEF66629879DE000FF6A89 /* ContactViewController.swift in Sources */,
+ 40B17FE129960FE000307F06 /* Extension+TableViewCell.swift in Sources */,
4FEEF67529879E1D00FF6A89 /* IOManager.swift in Sources */,
4FEEF66229879DE000FF6A89 /* AppDelegate.swift in Sources */,
- 4FAE8E8D298A526F006DCD46 /* ModelData.swift in Sources */,
+ 40B17FDB299609A300307F06 /* CellFormat.swift in Sources */,
+ 4FAE8E8D298A526F006DCD46 /* ContactsController.swift in Sources */,
+ 4F6D4AAE29960CE6001D2E8E /* NewContactDelegate.swift in Sources */,
+ 40F251B92996124100C7C5DA /* Extension+String.swift in Sources */,
4FEEF66429879DE000FF6A89 /* SceneDelegate.swift in Sources */,
- 4000CC3B298A59DC003C8B95 /* ContactError.swift in Sources */,
+ 4F6D4AAA2995E471001D2E8E /* ContactCell.swift in Sources */,
+ 4000CC3B298A59DC003C8B95 /* JSONCodable.swift in Sources */,
4FEEF67329879E0300FF6A89 /* Extension.swift in Sources */,
+ 40F251BB2996125600C7C5DA /* Extension+Character.swift in Sources */,
4FEEF67929879E2B00FF6A89 /* Phonebook.swift in Sources */,
- 4000CC39298A50F3003C8B95 /* TableViewDataSource.swift in Sources */,
4FEEF67629879E2200FF6A89 /* Error.swift in Sources */,
+ 40B17FDF29960E9400307F06 /* Reusable.swift in Sources */,
4FAE8E88298A4E60006DCD46 /* Extension+UserInfo.swift in Sources */,
4FEEF67729879E2500FF6A89 /* ContactManager.swift in Sources */,
4FEEF67829879E2800FF6A89 /* UserInfo.swift in Sources */,
4FEEF67429879E1900FF6A89 /* StringLiteral.swift in Sources */,
+ 4F6D4AAC299606CF001D2E8E /* Extension+TableView.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
diff --git a/ios-contact-manager-ui/ios-contact-manager-ui.xcodeproj/project.xcworkspace/xcuserdata/songsunjin.xcuserdatad/UserInterfaceState.xcuserstate b/ios-contact-manager-ui/ios-contact-manager-ui.xcodeproj/project.xcworkspace/xcuserdata/songsunjin.xcuserdatad/UserInterfaceState.xcuserstate
index a4e3b0e5..5c527317 100644
Binary files a/ios-contact-manager-ui/ios-contact-manager-ui.xcodeproj/project.xcworkspace/xcuserdata/songsunjin.xcuserdatad/UserInterfaceState.xcuserstate and b/ios-contact-manager-ui/ios-contact-manager-ui.xcodeproj/project.xcworkspace/xcuserdata/songsunjin.xcuserdatad/UserInterfaceState.xcuserstate differ
diff --git a/ios-contact-manager-ui/ios-contact-manager-ui/UserInfo.swift b/ios-contact-manager-ui/ios-contact-manager-ui/UserInfo.swift
index faa904d5..819a4457 100644
--- a/ios-contact-manager-ui/ios-contact-manager-ui/UserInfo.swift
+++ b/ios-contact-manager-ui/ios-contact-manager-ui/UserInfo.swift
@@ -28,7 +28,7 @@ enum UserInfoParameters: String {
}
}
-struct UserInfo: Codable {
+struct UserInfo: Codable, Hashable {
let name: String
let age: Int
let phone: String
@@ -50,22 +50,10 @@ struct UserInfo: Codable {
extension UserInfo: CustomStringConvertible {
var description: String {
- return "\(self.name) / \(self.age) / \(self.phone)"
+ return "\(name) / \(age) / \(phone)"
}
var addedDescription: String {
- return "\(self.age)세 \(self.name)(\(self.phone))"
- }
-}
-
-extension UserInfo: Hashable {
- static func == (lhs: UserInfo, rhs: UserInfo) -> Bool {
- return lhs.description == rhs.description
- }
-
- func hash(into hasher: inout Hasher) {
- hasher.combine(name)
- hasher.combine(age)
- hasher.combine(phone)
+ return "\(age)세 \(name)(\(phone))"
}
}