내일배움캠프 iOS

UIKit) TIL # 38 포켓몬 연락처 앱 과제 마무리, 트러블 슈팅

yjuni22 2024. 12. 11. 18:26

진행과정

https://yjuni22.tistory.com/57

 

UIKit) TIL # 35 포켓몬 연락처 앱 과제 (Lv 1 ~ 4)

과제 시작 전 구성 예상 새로운 과제는 연락처 앱이다.포켓몬 API 를 통한 랜덤 이미지 생성과 CoreData를 활용해 연락처 데이터를 저장하는 방식이다.API 연결에 대한 실습과 CoreData를 활용해볼 수

yjuni22.tistory.com

 

https://yjuni22.tistory.com/58

 

UIKit) TIL # 36 포켓몬 연락처 앱 과제 (Lv 5 ~ 8)

Lv 1 ~ 4 Linkhttps://yjuni22.tistory.com/57 UIKit) TIL # 35 포켓몬 연락처 앱 과제 (Lv 1 ~ 4)과제 시작 전 구성 예상 새로운 과제는 연락처 앱이다.포켓몬 API 를 통한 랜덤 이미지 생성과 CoreData를 활용해 연락

yjuni22.tistory.com

 

과제 외 추가 구현 내용

1. 상세 페이지의 GuideLabel 생성

2. 텍스트 필드로 구현

3. 포켓API 에서 다른 이미지로 받아오기

4. 전화번호 입력 시 hypen 자동생성 구현

5. 전화번호 텍스트필드 11자리까지만 입력 허용

6. 새로운 정보 추가 시 입력된 데이터가 없다면 알럿창 띄우기 ( 전화번호 9자리 이하 시 알럿창 )

7. 삭제기능, 정보가 있을 시 (셀클릭시) 삭제버튼 생성, 더블체크를 위한 알럿창 띄우기

8. 텍스트 필드 키보드 관련 ( 외부 터치 시 키보드 내리기, 전화번호 - 넘버패드 적용 )

9. preparForReuse 개념 이해


트러블 슈팅

네트워킹 코드 옮기기

기존 PhoneBookViewController에 구현되어 있는 Get메서드 분리
 @objc func randomButtonTapped(_ sender: UIButton) {
         fetchPoketmonData()
     }
     // 서버에서 포켓몬 데이터를 받아오는 메서드
    private func fetchPoketmonData() {
        let randomNum = Int.random(in: 1...1000)
        let url = URL(string: "\(requestUrl)\(randomNum)")
        guard let urlString = url else {
            print("잘못된 URL")
            return
        }
        networkManager.fetchDateByAlamofire(url: urlString) { [weak self] (result: Result<PoketmonData, AFError>) in
            guard let self else { return }
            switch result {
            case .success(let result):
                
                guard let imageUrl = URL(string: result.sprites.other.home.imageUrl) else { return }
                
                // Alamofire 를 사용한 이미지 로드
                AF.request(imageUrl).responseData { response in
                    if let data = response.data, let image = UIImage(data: data) {
                        DispatchQueue.main.async {
                            self.phoneBookView.randomImageView.image = image
                        }
                    }
                }
            case .failure(let error):
                print("데이터 로드 실패: \(error)")
            }
        }
    }

 

현재 PhoneBookViewController에 get 메서드가 구현되어 있다.

fetchPoketmonDate() 를 파일 분리를 위해 네트워크매니저 파일로 이동하고 싶은데

어떻게 수정하여 넣어야 할 지 감이 오지 않았다.

 

클로저를 사용하여 일부 수정을 해야할 것 같은데 

아직 미숙한 부분이라 고민을 해보다가

튜터님에게 도움을 요청했다.

 

현재 뷰컨트롤러 내에서 데이터요청과 결과를 처리하고 있다.

 

네트워크 매니저로 이동 시 외부에서 접근하여 메서드를 실행시키기 때문에

결과를 어딘가에 반환시켜야 그 결과를 처리할 수 있게 된다.

그렇기 때문에 결과를 반환시켜줄 클로저를 구현해주어야 한다.

수정된 코드 

네트워크 매니저 파일

 // Alamofire 를 사용해서 서버 데이터를 불러오는 메서드
    func fetchDateByAlamofire<T: Decodable>(url: URL, completion: @escaping (Result<T, AFError>) -> Void) {
        AF.request(url).responseDecodable(of: T.self) { response in
            completion(response.result)
        }
    }
    // 서버에서 포켓몬 데이터를 받아오는 메서드
    func fetchPoketmonData(completion: @escaping (Result<PoketmonData,AFError>) -> Void) {
        let randomNum = Int.random(in: 1...1000)
        let url = URL(string: "\(requestUrl)\(randomNum)")
        guard let urlString = url else {
            print("잘못된 URL")
            return
        }
        fetchDateByAlamofire(url: urlString) { [weak self] (result: Result<PoketmonData, AFError>) in
            completion(result)
        }
    }

 

수정된 메서드를 보면 (completion: @escaping (Result<PoketmonData,AFError>) 을 추가 구현하여

넘겨줄 결과를 클로저에 담아주고 있다.

 

PhoneBookViewController : 실제 사용 메서드 ( 랜덤이미지 생성 버튼 )

 // MARK: - API 랜덤 이미지 받아오기
    @objc func randomButtonTapped(_ sender: UIButton) {
        networkManager.fetchPoketmonData { [weak self] result in
            guard let self else { return }
            switch result {
            case .success(let result):
                
                guard let imageUrl = URL(string: result.sprites.other.home.imageUrl) else { return }
                
                // Alamofire 를 사용한 이미지 로드
                AF.request(imageUrl).responseData { response in
                    if let data = response.data, let image = UIImage(data: data) {
                        DispatchQueue.main.async {
                            self.phoneBookView.randomImageView.image = image
                        }
                    }
                }
            case .failure(let error):
                print("데이터 로드 실패: \(error)")
            }
        }
    }

 

버튼에서 fetchPoketmonData를 실행하면 결과를 클로저로 호출부에 반환시킨다.

반환된 클로저를 {  } 안에 result 를 성공, 실패 처리하여 이미지를 로드한다.

 

아직 클로저와 네트워킹에 대한 이해가 부족하여 조금 어려운 부분인 것 같다.

더 자세하게 공부해보아야 할 것 같다.

 

전화번호 텍스트필드 관련

phoneNumberTextField 의 count를 11자 초과시 입력이 안되게 해놓으니
저장된 데이터 클릭시 하이픈 때문에 13자가 나와 숫자를 지우면 총 12자리가 되어 수정이 안되는 상황 

 

해결방법 : 코어데이터에 저장된 정보를 불러올 때 hypen이 제거된 상태로 나오도록 수정

 

과정

 

텍스트 필드 관련 코드

    func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
        // 백스페이스 허용 코드
        if string.isEmpty {
            return true
        }
        if textField == phoneBookView.nameTextField {
            return Int(string) == nil // 이름 필드에 숫자 입력 안되게
        } else if textField == phoneBookView.phoneNumberTextField {
            // 11글자 넘어가면 입력을 막는 코드
            if (textField.text?.count)! + string.count > 11 {
                return false
            }
            return Int(string) != nil // 전화번호 필드에 문자 입력 안되게
        }
        return true
    }
    
}
// MARK: - 휴대폰 번호 하이픈 추가
extension String {
    
    public var withHypen: String {
        var stringWithHypen: String = self
        guard self.count == 10 || self.count == 11 else { return self } // 10자리나 11자리만 처리
        
        stringWithHypen.insert("-", at: stringWithHypen.index(stringWithHypen.startIndex, offsetBy: 3))
        stringWithHypen.insert("-", at: stringWithHypen.index(stringWithHypen.endIndex, offsetBy: -4))
        
        return stringWithHypen
    }
}

 

전화번호 텍스트 필드에 11자리가 넘어갈 시 입력을 허용하지 않도록 하는 코드를 구현하고

10자리나 11자리일 시 하이픈을 추가하여 코어데이터에 저장되도록 하였다

 

 

문제상황 및 고민 : 

'-' 을 포함하여 텍스트필드의 count가 세져서 더이상 입력이 되지 않았다.

저장된 정보를 받아올 때 '-' 뺀 전화번호로 가져오기
   /// 하이픈 제거 메서드
    public var withoutHyphen: String {
        return self.replacingOccurrences(of: "-", with: "")
    }

 

extension String 에 하이픈 제거 메서드를 추가 구현하여

저장된 정보를 수정할 때에는 하이픈이 제거된 상태로 불러오도록 구현하여 문제를 해결하였다.

 

@objc func applyButtonTapped() {
        //적용버튼 눌렀을 때
        let name = phoneBookView.nameTextField.text
        let phone = phoneBookView.phoneNumberTextField.text?.withHypen
        let image = phoneBookView.randomImageView.image
        if (name?.count != 0) && (phone!.count > 9) && image != nil {
            // 이미지의 경우 JPEG로 변환하여 코어데이터에 저장 후 pop
            let imageData = image?.jpegData(compressionQuality: 0.5)
            coreDataManager.saveMemberData(name: name, phone: phone, image: imageData) {
                print("저장 완료")
                self.navigationController?.popViewController(animated: true)
            }
        } else {
            let alert = UIAlertController(title: "적용 실패", message: "올바른 정보를 입력해주세요!", preferredStyle: .alert)
            let succes = UIAlertAction(title: "확인", style: .default)
            alert.addAction(succes)
            present(alert, animated: true)
        }
    }

 

추가적으로 생성시와 수정시 정보가 입력되어있지 않거나, 숫자가 9자리 이하거나,

이미지가 없으면 적용되지 않도록 알럿창을 띄우게 구현하였다.


PrepareForReuse

 // 셀이 재사용되기 전에 호출되는 메서드
    override func prepareForReuse() {
        super.prepareForReuse()
        // 이미지가 바뀌는 것처럼 보이는 현상을 없애기 위함
        self.mainImageView.image = nil
    }

 

셀은 스크롤 할때 가장 위의 셀이 맨 아래로 재사용된다.

그때 셀이 재사용 되는 속도보다 이미지가 늦게 사라져 일어날 수 있는 오류를 방지하기 위해

셀이 재사용 되기 전에 다음과 같은 코드를 추가구현해주어야 한다.

View를 다시 사용할 수 있도록 준비하는 데 필요한 초기화(clean-up)를(을) 수행합니다.

 

 


참고자료)

 

https://ios-daniel-yang.tistory.com/entry/UITextField

 

[iOS/Swift] UITextField 설정

텍스트 필드 여러 설정들 UI 관련 textField.frame.size.height = 22 // 프레임 높이 textField.borderStyle = .roundedRect // 테두리 스타일 textField.autocorrectionType = .no // 자동 수정 활성화 여부 textField.spellCheckingType =

ios-daniel-yang.tistory.com

 

https://green1229.tistory.com/269

 

기본 타입을 Extension하여 편리하게 사용하기

안녕하세요. 그린입니다🟢 이번 포스팅에서는 기본 타입을 Extension해서 조금 유의미하게 프론트 개발에서 사용하는것을 포스팅해보려 합니다🙋🏻 대게 앱 개발을 하다보면 서버에서 내려온

green1229.tistory.com

 

https://velog.io/@jaonlee0223/Cell%EC%9D%98-%EC%9E%AC%EC%82%AC%EC%9A%A9%EC%84%B1%EC%97%90-%EB%8C%80%ED%95%B4-with-prepareForReuse

 

Cell의 재사용성에 대해 (with prepareForReuse())

CollectionView를 사용하며 Cell을 초기화할 때 사용하는 prepareForReuse()에 대해 알아보자!

velog.io