private func existingContactEditing() {
guard let contact = contact else { return }
title = contact.name // 네비게이션 바 제목을 연락처 이름으로 변경
nameTextField.text = contact.name // 기존 이름
phoneTextField.text = contact.phoneNumber // 기존 번호
if let imageData = contact.profileImage, let image = UIImage(data: imageData) {
profileImageView.image = image
}
}
네비게이션 바의 title을 연락처의 이름으로 설정해서, 사용자에게 현재 수정 중인 연락처를 직관적으로 알려주고,
기존 연락처(contact)의 데이터를 가져와 nameTextField
와 phoneTextField
에 표시한다.
그러면 사용자는 기존 데이터를 보고 필요한 경우 수정할 수 있게 된다.
연락처에 저장된 profileImage
데이터가 있으면 이걸 UIImage
로 변환하여 profileImageView
에 표시한다.
"왜 프로필 이미지의 경우에는 저렇게 초기화를 해줘야 하지?
그냥 텍스트필드처럼 불러오기만 하면 안되나?"
텍스트 필드는 단순히 문자열을 String 타입으로 저장하고 불러오면 되지만,
이미지의 경우는 Data로 저장된 이진 데이터이기 때문에 변환 과정을 거쳐야 한다.
UIImage
는 이미지 데이터를 Data
형식으로 저장했고,
이것을 다시 UIImage 객체로 변환해서 사용해야 하기 때문이다. 아우 사실 나도 어렵다.
// 연락처 수정
func updateContact(contact: Contact, name: String, phoneNumber: String, profileImage: Data?) {
contact.name = name
contact.phoneNumber = phoneNumber
contact.profileImage = profileImage
saveContext()
}
아 그리고 연락처를 수정할 때, 기존 연락처가 화면에 제대로 표시되지 않는 문제를 겪었다.
세부연락처 화면으로 넘어갈 때 타이틀의 이름이 제대로 전달되지 않아서 발생한 문제였는데,
우선 PhoneBookViewController
에서 existingContactEditing()
메서드를 호출해 수정하려는 연락처의 정보를 텍스트 필드에 바인딩하는 부분을 확인했다.
이 메서드가 viewDidLoad()에서 호출되기 전에 contact 값이 제대로 설정되도록 처리해야 했고
수정된 내용은 CoreDataManager를 통해 연락처 데이터를 업데이트 한 뒤
수정된 데이터를 반영하기 위해 이전 화면으로 돌아가면서 테이블 뷰를 갱신했다.
연락처 이름을 화면 제목으로 설정하는 로직이 필요했기 때문에, existingContactEditing()에서 contact.name을 title로 설정하는 부분이 추가되었습니다.
이렇게 하면 연락처 수정 화면에서 제목이 contact.name으로 바뀌게 되어서 해당 연락처가 누구인지 확인할 수 있었다.
if let existingContact = contact {
CoreDataManager.shared.updateContact(contact: existingContact, name: name, phoneNumber: phoneNumber, profileImage: imageData)
} else {
CoreDataManager.shared.addContact(name: name, phoneNumber: phoneNumber, profileImage: imageData)
}
과제에서 또 하나의 뷰컨트롤러를 만들지말고 재사용하라는 말이 있었다.
확실히 하나의 뷰 컨트롤러에서 추가도 하고 수정도 하면 코드 중복도 줄이고 좀 더 단순화가 된다.
별도의 "연락처 추가"
화면과 "연락처 수정"
화면을 만들 필요가 없어어졌다는 뜻이다.
그리고 추가/수정
이 같은 화면 안에서 처리되기 때문에,
연락처 데이터 변경 방식이 바뀌더라도 하나의 뷰 컨트롤러만 수정하면 되어서 편하다.
위 코드는 contact
가 존재한다면, 이미 있는 연락처를 수정한다는 것인데,
아까 추가한 수정 코어데이터 CoreDataManager.shared.updateContact
를 호출해 데이터베이스에서 해당 연락처를 업데이트 해준다.
그런데 만약 contact
가 nil
이라면 새 연락처로 간주해서
CoreDataManager.shared.addContact
를 호출해 새로운 연락처를 데이터베이스에 추가한다는 것.
아래의 삭제버튼이 있는건 이미 했다는 뜻
UI 추가하고 오토레이아웃 잡아주고 그렇게 버튼을 정성스럽게 만들어준 뒤,
@objc private func deleteButtonTapped() {
print("삭제 버튼 눌림")
guard let contact = contact else { return }
// CoreData에서 연락처 삭제
CoreDataManager.shared.deleteContact(contact: contact)
// 이전 화면으로 돌아가기
navigationController?.popViewController(animated: true)
}
연락처 삭제 메서드를 만들었다.
연락처가 nil 이 아닐경우 화면에 표시된 연락처 삭제를 누르면 코어데이터매니저 파일에 있는 deleteContact가 실행되고 삭제됨과 동시에 다시 테이블뷰 화면으로 돌아간다.
액션은 따로 모아두었는데
private func setupActions() {
// 랜덤 이미지 생성 버튼 액션
detailView.randomButton.addTarget(self, action: #selector(generateRandomImage), for: .touchUpInside)
// 삭제 버튼 액션
detailView.deleteButton.addTarget(self, action: #selector(deleteButtonTapped), for: .touchUpInside)
}
이렇게 setupActions 를 만들어 버튼액션만 따로 묶어두었다.
복잡한 코드를 안 복잡해보이게 짜는게 정말 대단한 능력이라고 생각한다.
원래의 api 코드를 Alamofire
를 이용해서 리팩토링 작업을 했다.
Alamofire
사용 후 코드는 네트워크 요청 및 응답 처리가 훨씬 간결하고 효율적이었다.
그리고 이미지 다운로드나 JSON 디코딩과 같은 작업을 처리할 때 직접 처리할 필요 없이 Alamofire
가 대부분의 작업을 자동으로 처리해준다.
기존 코드는 직접적으로 URLSession
과 DispatchQueue
를 사용해서 네트워크 요청 및 데이터를 처리하고, 여러 단계를 거쳐야 해서 코드가 길고 복잡했다.
Alamofire
가 네트워크 작업과 관련된 다양한 세부 사항들울 싹 다 간소화시켜주고,
디코딩 과정까지 자동화해 준다는 점이 아 이걸 쓰는 이유가 있구나 싶었다.
오늘 작업은 기능 구현과 리팩토링이 적절히 결합된 좋은 경험이었다.
특히 Alamofire를 사용하면서 네트워크 요청 부분이 훨씬 간결한 느낌이라 좋았지만 디버깅이 필요했던 부분들이 좀 있었고,
그런 각 기능을 구현할 때 데이터를 어떻게 전달할지 어떻게 갱신할지를 신경 써야 한다는 점을 생각하며 한 것 같다.
데이터 전달과 상태 관리가 중요한 만큼 상태 변화와 관련된 로직을 명확히 처리해주고,
디버깅을 통해 예상치 못한 문제를 빠르게 찾아내는 능력을 키워야겠다고 생각한다.
이번 과제에서 코어데이터도 중요하지만 후반부에는 api를 다루는 부분에 좀 더 초점을 맞춰본 것 같다.
api는 어딜 가든 써야할 상황이 생길테니..!
와 앱 만든 거 먼데
짱인데