Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
1020789
feat: Navigation bar 추가, 새 view controller 스토리보드에 생성
vanism2091 Feb 6, 2023
b3eea1a
feat: 새연락처 추가 화면 layout 구성
SunnnySong Feb 6, 2023
f233bce
feat: 연락처 추가 화면 TextField 별 Keyboard 설정
vanism2091 Feb 6, 2023
8e419b1
feat: AddContactView View Controller 추가
vanism2091 Feb 6, 2023
69b5763
feat: 연락처 hyphen 처리 함수 구현 test
vanism2091 Feb 6, 2023
c9e4ec2
feat: Hyphen 관련 refactoring
vanism2091 Feb 7, 2023
30ef6f1
refactor: LocationsOfHyphen 상수화, 주석 제거
vanism2091 Feb 7, 2023
7f2a628
chore: 폴더 구조 변경
SunnnySong Feb 7, 2023
52196ef
feat: save, cancel 버튼 Alert 생성
SunnnySong Feb 7, 2023
c3fc777
chore: 지금은 사용하지 않는 변수 _ 처리
vanism2091 Feb 10, 2023
3524eae
chore: Rename UserInfo computed property
vanism2091 Feb 10, 2023
55e72e8
feat: UITableViewDiffableDataSource 구현
vanism2091 Feb 10, 2023
412e2d0
feat: JSONCodable protocol 생성 및 ModelData 생성
SunnnySong Feb 10, 2023
afdf538
Merge branch 'Step3-protocol' into Step3
SunnnySong Feb 10, 2023
bb48077
merge: Step3 merge with Step3-protocol
SunnnySong Feb 10, 2023
33d74ac
feat: load()함수 삭제, private(set)으로 변경
SunnnySong Feb 10, 2023
10d6d63
chore: JSONError의 localizedDescription 정의 수정
vanism2091 Feb 10, 2023
3052e8f
feat: DIffableDataSource에 swipe remove 구현
vanism2091 Feb 10, 2023
d432b82
feat: custom ContactCell 생성 및 tableView 연결
SunnnySong Feb 10, 2023
60b2c8c
feat: 연락처 hyphen 처리 함수 구현 test
vanism2091 Feb 6, 2023
2db829e
chore: fix merge error...
vanism2091 Feb 10, 2023
2f50ef3
fix: merge를 해결하며 발생한 오류 수정
vanism2091 Feb 10, 2023
045de4c
chore: Protocols directory 생성
vanism2091 Feb 10, 2023
fdc8904
feat: dequeueresuablecell 관련 코드 extension 정의
SunnnySong Feb 10, 2023
b6508ee
Merge branch 'Step3-extension' into Step3
SunnnySong Feb 10, 2023
4d97263
feat: 연락처 추가 구현 및 연락처 encoding
SunnnySong Feb 10, 2023
14f6592
refactor: 은닉화 적용, 컨벤션 통일
vanism2091 Feb 10, 2023
279f6aa
refactor: UserInfo Hashable extension 수정
vanism2091 Feb 10, 2023
daf7dda
feat: file error 추가
vanism2091 Feb 10, 2023
059a47f
style: 붙이지 않아도 되는 self 삭제
vanism2091 Feb 10, 2023
949c28b
feat: AddContactView에서 TextField AutoLayout Dynamic type 적용
vanism2091 Feb 10, 2023
485f186
fix: hyphen 추가 로직 수정
vanism2091 Feb 10, 2023
9791041
refactor: Literal Number 10을 상수로 설정
vanism2091 Feb 10, 2023
8810ce3
style: 주석 파일 이름 수정
vanism2091 Feb 10, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified ios-contact-manager-ui/.DS_Store
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -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
}
}
Comment on lines +31 to +50
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오우... 결국 각 케이스를 구분해서 정리하셨군요
고생하셨습니다 😆


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 { "-" }
}
57 changes: 0 additions & 57 deletions ios-contact-manager-ui/ContactManagerUI/Base.lproj/Main.storyboard

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -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).
}
Expand Down
14 changes: 0 additions & 14 deletions ios-contact-manager-ui/ContactManagerUI/ContactError.swift

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -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 ..<contactTextHyphenPolicyLength:
return relocateHyphen(of: digitString, at: LocationsOfHyphen.forTwoThreeFour)
case contactTextHyphenPolicyLength:
return relocateHyphen(of: digitString, at: LocationsOfHyphen.forTwoFourFour)
default:
return relocateHyphen(of: digitString, at: LocationsOfHyphen.forThreeFourFour)
}
}

private func removeLastHyphen(from text: String) -> 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
}
}
Original file line number Diff line number Diff line change
@@ -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<Section, UserInfo> {
typealias Snapshot = NSDiffableDataSourceSnapshot<Section, UserInfo>

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