스파르타 코딩 클럽 - iOS 스타터 6기/본 캠프

35. 스파르타 코딩 클럽 - 포켓몬 연락처 앱 만들기 (2)

seongpil Heo 2025. 4. 18. 21:19

 🧑‍💻 오늘까지 진행한 요구사항

 

오늘은 Level4를 구현하였다.

요구사항은 랜덤 이미지 생성 버튼을 만들고, 버튼을 클릭할때마다 PokeAPI를 이용해서 랜덤한 포켓몬 이미지를 불러온 뒤

불러온 이미지를 ImageView에 넣어주는 것이다.

 

데이터는 JSON 형태로 받아오며, PokeAPI에는 많은 정보가 있지만

id, name, height, weight, 이미지를 위한 front_default 정도만 받아온다.

 

[ 버튼 만들기 ]

private lazy var button: UIButton = {
        let button = UIButton()
        button.setTitle("랜덤 이미지 생성", for: .normal)
        button.setTitleColor(.blue, for: .normal)
        button.addTarget(self, action: #selector(buttonTapped), for: .touchDown)
        return button
    }()
    
    
@objc private func buttonTapped() {
        print("랜덤 이미지 생성 버튼 클릭")
        fetchPokemonData()
    }

 

우선 랜덤 이미지 생성 버튼을 하나 만들고, 버튼을 클릭하면 이벤트를 수행할 buttonTapped() 함수를 만들었다.

buttonTapped() 메서드 안에는 버튼을 클릭하면 콘솔창에 "랜덤 이미지 생성 버튼 클릭" 이라는 Print문을 하나 작성했고,

fetchPokemonData() 라는 메서드를 작성했다.

 

fetchPokemonData() 라는 메서드는 API통신을 통해 https://pokeapi.co/  에서 데이터를 받아오는 메서드이다.

 

나는 이번 과제에서 URLSession 대신 Alamofire를 이용해서 데이터 통신을 했다.

Alamofire를 사용한 이유는 코드를 보다 간결하게 작성할 수 있고, switch문을 사용해서 성공과 실패로 분기 처리가 쉽기 때문이다.

 

[ fetchData 함수 만들기 ]

// Alamofire를 이용해서 데이터 받기
    private func fetchData<T: Decodable>(url: URL, completion: @escaping (Result<T, AFError>) -> Void) {
        AF.request(url).responseDecodable(of: T.self) { response in
            completion(response.result)
        }
    }

 

먼저 fetchData라는 함수를 만들었다.

이 함수는 데이터 통신을 위한 기본 함수이다.

 

 

[ fetchPokemonData 함수 만들기 ]

// Alamorife를 이용해서 포켓몬 API 에게 데이터 요청하기
    private func fetchPokemonData() {
        let randomNumber = Int.random(in: 1...1000)
        print(randomNumber)
        let urlComponents = URLComponents(string: "https://pokeapi.co/api/v2/pokemon/\(randomNumber)")
        
        guard let url = urlComponents?.url else {
            print("잘못된 URL")
            return
        }
        
        fetchData(url: url) { [weak self] (result: Result<PokemonResult, AFError>) in
            guard let self else { return }
            switch result {
            case .success(let result):
                print("id: \(result.id), name: \(result.name), weight: \(result.weight), height: \(result.height)")
                
                guard let imageUrl = URL(string: result.sprites.front_default) else { return }
                // 이미지 뷰에 받아온 이미지 넣기
                AF.request(imageUrl).responseData { response in
                    if let data = response.data, let image = UIImage(data: data) {
                        DispatchQueue.main.async {
                            self.circleImageView.image = image
                        }
                    }
                }
                
            case .failure(let error):
                print("데이터 로드 실패 : \(error)")
                
            }
        }
        
    }

 

다음으로는 직접적으로 API와 통신 할 fetchPokemonData()를 작성하였다.

먼저 랜덤 이미지를 생성해야하기 때문에 랜덤 넘버를 생성할 수 있는

randomNumber 변수에 Int.random(in: 1...1000)을 사용해서 1부터 1000까지의 정수 중에

랜덤으로 하나를 randomNumber에 넣어준다.

 

let urlComponents = URLComponents(string: "https://pokeapi.co/api/v2/pokemon/\(randomNumber)")
        
        guard let url = urlComponents?.url else {
            print("잘못된 URL")
            return
        }

 

그 다음 내가 필요한 정보를 주는 API의 주소를 URLComponents를 이용해서 urlComponents 변수에 넣어준다.

 

fetchData(url: url) { [weak self] (result: Result<PokemonResult, AFError>) in
            guard let self else { return }
            switch result {
            case .success(let result):
                print("id: \(result.id), name: \(result.name), weight: \(result.weight), height: \(result.height)")
                
                guard let imageUrl = URL(string: result.sprites.front_default) else { return }
                // 이미지 뷰에 받아온 이미지 넣기
                AF.request(imageUrl).responseData { response in
                    if let data = response.data, let image = UIImage(data: data) {
                        DispatchQueue.main.async {
                            self.circleImageView.image = image
                        }
                    }
                }
            case .failure(let error):
                print("데이터 로드 실패 : \(error)")
            }
        }

 

그 다음으로 fetchData() 메서드를 수행한다.

성공했을 때 받을 result는 PokemonResult 타입의 struct 구조이고,

struct PokemonResult: Codable {
    let id: Int
    let name: String
    let height: Int
    let weight: Int
    let sprites: Sprites
}

struct Sprites: Codable {
    let front_default: String
}

PokemonResult는 위와 같다.

 

switch문을 이용해서 데이터 통신에 성공했을 때는 case .success: 부분을 수행하고

실패했을 때는 case .failure 부분을 수행한다.

 

성공했을 때는 받아온 데이터들을 콘솔에 한번 다 프린트 하고

이미지 뷰에 받아온 이미지 데이터를 넣어준다.

 

실패했을 때는 " 데이터 로드 실패"를 콘솔에 error와 함께 프린트 한다.


  🎯 Trouble Shooting

처음 받아온 데이터를 이미지 뷰에 넣어주려고 했는데 오류가 발생했었다.

이 오류는 self.circleImageView.image에 UIImage 타입을 넣어야 하는데,

현재 String 타입(즉, 이미지의 URL 문자열)을 넣으려고 해서 발생한 것이다.

 

guard let imageUrl = URL(string: result.sprites.front_default) else { return }
    // 이미지 뷰에 받아온 이미지 넣기
    AF.request(imageUrl).responseData { response in
        if let data = response.data, let image = UIImage(data: data) {
            DispatchQueue.main.async {
                self.circleImageView.image = image
            }
        }
    }

이 문제를 해결하기 위해 URL을 사용하여 imageUrl에 받아온 이미지 주소를 넣고,

 

Alamofire의 AF.request()를 사용하여 imageUrl에 HTTP 요청을 보내고.

.responseData는 서버로부터 응답 데이터를 바이트 형식(Data)으로 받아옵니다.

 

받아온 데이터를 UIImage를 사용해서 image 변수에 넣어주고,

self.circleImageView.image 에 image 값을 넣어줍니다.

 

이미지를 그리는 작업은 반드시 메인 쓰레드에서 작업해야 하기 때문에

DispatchQueue.main.async 에서 작업합니다.

 

이러한 방식으로 해당 오류를 해결했습니다.

 

 


📱 Simulator