Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

연락처 관리 앱 [Step3] 이지, 카일 #92

Open
wants to merge 35 commits into
base: d_easy
Choose a base branch
from

Conversation

Changhyun-Kyle
Copy link
Contributor

@junbangg
안녕하세요, 알라딘!
STEP3 PR 보내드립니다..!
많이 늦어졌지만 마무리 지어 보았습니다. 시간되실때 확인 해 주시면 감사드리겠습니다! 🙏


🧐 고민했던점

  • ContactManageViewController의 재사용

    • 연락처 추가화면연락처 수정화면의 인터페이스가 동일하여 기존 AddContactViewControllerContactManageViewController로 변경하여 재사용하였습니다.
    • 또한, 생성자를 통해 두 화면을 구분하여 활용할 수 있게 구현했습니다.
    • 우선, navigationTitle이 생성자를 통해 화면전환 시 주입되어 변경될 수 있게 구현했습니다.
    • 뿐만 아니라, 연락처 변경 시 필요한 선택된 해당 Contact 데이터를 옵셔널로 선언하여 nil일 경우 연락처 추가 로직, nil이 아닐 경우 연락처 수정 로직이 실행될 수 있게 구현했습니다.
    // ContactManageViewController
    // MARK: - Init
    init?(contactManager: ContactManager, contact: Contact? = nil, navigationBarTitle: String, delegate: UpdateContactDelegate?, coder: NSCoder) {
        self.contactManager = contactManager
        self.contact = contact
        self.navigationBarTitle = navigationBarTitle
        self.delegate = delegate
        super.init(coder: coder)
    }
    
    /*{code}*/
    
    @IBAction private func tappedSaveButton(_ sender: UIBarButtonItem) {
        /*{code}*/
        switch validation {
        case .success(var newContact):
            guard let contact = contact else {
                contactManager.addContact(contact: newContact)
                delegate?.updateTableViewForAdd()
                dismiss(animated: true)
                return
            }
            newContact.id = contact.id
            contactManager.editContact(contact: newContact)
            delegate?.updateTableViewForEdit()
            navigationController?.popViewController(animated: true)
    
    연락처 추가 연락처 수정
    image image
  • reloadData() VS insertRows()

    • 연락처를 추가할 때, 현재 프로젝트 명세서 상 한번에 하나의 연락처만 추가될 수 있습니다.
    • 따라서, 하나의 row를 추가하는 작업에 전체 tableViewreload()하는 것은 비효율적이라고 판단했습니다.
    • 기존 연락처의 가장 마지막에 append 되는 로직이기 때문에 insertRows()indexPath.row연락처의 갯수 -1 만큼으로 지정하여 tableView에 추가할 수 있게 구현했습니다.
    // ContactManager
     var contactRow: Int {
         return contactsCount - 1
     }
     // ContactListViewController
     func updateTableViewForAdd() {
         contactTableView.insertRows(
             at: [
                 IndexPath(row: contactManager.contactRow, section: 0)
             ],
             with: .automatic)
     }

🤔 조언을 얻고 싶은 부분

  • 연락처 검색 시 필터된 값을 어떻게 보여줄 것인가

    • 연락처 검색 시 필터된 데이터를 어떤 방식으로 보여줄 것인지에 대한 고민을 정말 많이 했습니다.
    • 크게 두가지 방법으로 생각해보았습니다.
    • 우선, 첫번 째 방법은 원본 데이터 배열과 필터(검색)된 데이터 배열을 따로 관리하는 방법입니다.
    • 해당 방법은 isFiltered라는 Boolean 타입을 통해 기존 데이터를 보여줄 지, 필터된 데이터를 보여줄 지 분기합니다.
    • 두 객체에 대한 역할을 명확하지만, 문제는 모든 로직에서 필터된 값과 원본 값을 보여주는 분기처리를 해야하는 번거로움이 발생했고 이는 비효율적인 로직이라는 생각이 들었습니다.
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return isFiltered ? contactManager.filteredContactsCount : contactManager.contactsCount
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        
        /*{code}*/
        
        let contact = isFiltered ? 
                        contactManager.filteredContact(row: indexPath.row) 
                        : contactManager.contact(row: indexPath.row)
        cell.setUpCell(with: contact)
        return cell
    }
    
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    
        /*{code}*/
                
        return isFiltered ?
                ContactManageViewController(contactManager: contactManager,
                                            contact: contactManager.filteredContact(row: indexPath.row),
                                            navigationBarTitle: "연락처 변경",
                                            delegate: self,
                                            coder: coder)
                : ContactManageViewController(contactManager: contactManager,
                                              contact: contactManager.contact(row: indexPath.row),
                                              navigationBarTitle: "연락처 변경",
                                              delegate: self,
                                              coder: coder)
            })
    /*{code}*/
    }
    • 위 방법은 텍스트필드에 값이 입력되지 않는 하나의 경우를 위해 분기처리가 반복되야 하는것에 의문을 느껴서 다른 방법을 고민해 보았습니다.
    • 두번째 방법contacts실제 데이터 저장소, filteredContactscontacts에서 값을 받아와 유저에게 보여지는 임시 저장소 개념으로 생각하여 아래와 같이 구현해 보았습니다.
    • 이로써 isFiltered의 분기처리를 하지 않고 두 개의 배열이 각자 역할이 분명해지긴 했지만 filteredContacts 가 호출되는곳이 많아지면서 위치에따라 네이밍이 적절하지 못하게 느껴지기도 합니다.
      ex) 초기화면에서부터 filteredContacts인 것이 부적절
      알라딘 이라면 어떤 생각이실지 궁금합니다!
    // searchText 비어있을 시에도 filteredContacts를 표시하도록 함
    func filteredContacts(by searchText: String) {
        if searchText == "" {
            filteredContacts = contacts
        } else {
            filteredContacts = contacts.filter {
                $0.name.localizedCaseInsensitiveContains(searchText)
            }
        }
    }
    
    // textField의 text상태에따라 viewWillAppear 시점에도 초기화 + 리로드
    override func viewWillAppear(_ animated: Bool) {
        navigationController?.isNavigationBarHidden = false
        contactManager.filteredContacts(by: (navigationItem.searchController?.searchBar.text)!)
        contactTableView.reloadData()
    }
  • textfield의 입력을 제한하는 방법

    • 텍스트필드에 숫자만을 입력할 수 있도록 할 때 위 처럼 키보드 타입을 바꿔서 사용자의 입력을 자연스럽게 제한하는 방법이 있지만 사실 이것만으로는 모든 경우를 막을수는 없어 보입니다. (블루투스 키보드 등)
    • 그래서 저희 코드에는 구현하지 않았지만 정규식과 UISearchBarDelegatetextDidChange 메서드를 활용해 텍스트필드에 예외적인 문자가 유지되지 않도록 하는 방법을 생각해보았습니다.
    • 이렇게 사용자의 입력을 제한해야되는 경우 자주 쓰이는 방법이나 알라딘의 의견이 궁금합니다..!

Changhyun-Kyle and others added 30 commits January 12, 2024 17:15
refactor: 불필요 주석 제거
- UpdateContactDelegate 채택
- 기존 AddContactController를 재사용하여 연락처 수정화면 구현
Comment on lines +56 to +57
// MARK: - Private Methods
extension ContactListViewController {
Copy link
Contributor

Choose a reason for hiding this comment

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

👍
그냥 private extension 으로 하고 안에 있는 메서드들은 private 떼는 방법도 있어요! 참고만 해주세요~

Comment on lines +118 to +127
ContactManageViewController(contactManager: contactManager,
contact: contactManager.filteredContact(row: indexPath.row),
navigationBarTitle: "연락처 변경",
delegate: self,
coder: coder)
: ContactManageViewController(contactManager: contactManager,
contact: contactManager.contact(row: indexPath.row),
navigationBarTitle: "연락처 변경",
delegate: self,
coder: coder)
Copy link
Contributor

Choose a reason for hiding this comment

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

생성자/메서드 인덴트 컨벤션이 다른 곳이랑 달라보이네요!

Comment on lines +8 to +9
extension String {
var formattedPhoneNumber: String {
Copy link
Contributor

Choose a reason for hiding this comment

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

흠 이거 익스텐션으로 두신 이유가 궁금합니다!

navigationItem.hidesSearchBarWhenScrolling = false
contactSearchController.searchBar.delegate = self
contactSearchController.hidesNavigationBarDuringPresentation = false
contactSearchController.searchBar.placeholder = "이름을 입력해주세요."
Copy link
Contributor

Choose a reason for hiding this comment

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

요런 문자열들을 네임스페이스에서 관리하는것은 어떨까요??

creator: { coder in
return ContactManageViewController(
contactManager: self.contactManager,
navigationBarTitle: "새 연락처",
Copy link
Contributor

Choose a reason for hiding this comment

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

요런것들도요!

Comment on lines +120 to +125
navigationBarTitle: "연락처 변경",
delegate: self,
coder: coder)
: ContactManageViewController(contactManager: contactManager,
contact: contactManager.contact(row: indexPath.row),
navigationBarTitle: "연락처 변경",
Copy link
Contributor

@junbangg junbangg Jan 24, 2024

Choose a reason for hiding this comment

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

요런 문자열들도요ㅎ

phoneNumberTextField.addTarget(self,
action: #selector(phoneNumberTextFieldEditingChanged),
for: .editingChanged)
guard let contact = contact else { return }
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
guard let contact = contact else { return }
guard let contact else { return }

@@ -7,70 +7,134 @@

import UIKit

final class ContactListViewController: UIViewController {
final class ContactListViewController: UIViewController, UpdateContactDelegate {
Copy link
Contributor

Choose a reason for hiding this comment

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

Delegate도 익스텐션르로 채택을 하면 관련 메서드를 한눈에 보기 쉬워서 강추합니다!

Copy link
Contributor

@junbangg junbangg left a comment

Choose a reason for hiding this comment

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

수고하셨습니다!!!
이것저것 많이 고민하신 흔적이 느껴졌습니다:)

질문 주신 내용들은 관련 코드에 코멘트 남겼습니다!🙋🏻‍♂️

Comment on lines +118 to +124
ContactManageViewController(contactManager: contactManager,
contact: contactManager.filteredContact(row: indexPath.row),
navigationBarTitle: "연락처 변경",
delegate: self,
coder: coder)
: ContactManageViewController(contactManager: contactManager,
contact: contactManager.contact(row: indexPath.row),
Copy link
Contributor

Choose a reason for hiding this comment

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

코멘트를 주신거 보고 이거에 대한 저의 개인적인 의견을 드리자면
분기를 쳐서 ContactManageViewController 를 생성하는 코드가 이렇게 두번씩 있는건 불필요해 보이네요..!
지금
contact: contactManager.filteredContact(row: indexPath.row)
contact: contactManager.contact(row: indexPath.row)
빼고는 다 똑같은데 말이죠..!

그렇다면 그냥 다음과 같이 할 수 있을것 같네요!

let contact = isFiltered ? contactManager.filteredContact(row: indexPath.row) : contactManager.contact(row: indexPath.row)

return ContactManageViewController(인자, contact: contact, 인자, 인자..) 

그리고 이렇게 리팩토링을 하면 또 한가지 개선점이 보이는데요..!
filteredContact(row: Int)contact(row: Int) 를 굳이 따로 둘 필요가 없어보이지 않나요?!
하나의 메서드로 합칠 수도 있을것 같네요!

let contact = contactManager.contact(for: indexPath.row, when: isFiltered)

return ContactManageViewController(인자, contact: contact, 인자, 인자..) 

질문 주셨던 다음과 같은 상황도 마찬가지로 contactManager 가 알아서 index랑 cell 을 주도록 리팩토링 할 수 있을것 같네요..!

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return isFiltered ? contactManager.filteredContactsCount : contactManager.contactsCount
}

...

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    
    /*{code}*/
    
    let contact = isFiltered ? 
                    contactManager.filteredContact(row: indexPath.row) 
                    : contactManager.contact(row: indexPath.row)
    cell.setUpCell(with: contact)
    return cell
}

Copy link
Contributor

Choose a reason for hiding this comment

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

그리고 또 한가지 의문점이 생긴것은,
ContactManageViewController 가 지금 ContactManager를 의존하고 있는데,
생성시에 contact 를 꺼내서 따로 집어 넣는 이유가 있나요???🤔

Comment on lines +51 to +53
nameTextField.keyboardType = .default
ageTextField.keyboardType = .numberPad
phoneNumberTextField.keyboardType = .phonePad
Copy link
Contributor

Choose a reason for hiding this comment

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

코멘트로 질문 주신 내용이 요기 같아서 답변을 드리자면🤔
요구사항에 따라 다르겠지만 이런식으로 제공하는 키보드를 강제하는 방법도 하나의 방법 같긴 합니다..!
그런데 말씀해주신것 처럼 보통은 사용자 입력을 받을때 validation 로직이 붙는 경우가 많아서 그걸 이용하여 사용자에게 올바른 입력을 유도 하는 경우가 많은것 같아요..!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants