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))" } }