Ejemplo práctico de cómo usar URLSession con JSONDecoder
Ejemplo práctico de cómo usar URLSession con JSONDecoder

URLSession + JSONDecoder en Swift y SwiftUI en Español

URLSession y JSONDecoder en Swift es la combinación perfecta para realizar una petición HTTP y transformar el JSON a un modelo de nuestra app iOS. Hoy veremos un ejemplo práctico haciendo una petición a una URL y mostrando los datos en SWIFTUI (JSON Swift)

SwiftBeta

Tabla de contenido


👇 SÍGUEME PARA APRENDER SWIFTUI, SWIFT, XCODE, etc 👇
URLSession y JSONDecoder en Swift
URLSession y JSONDecoder en Swift

Hoy lo que vamos a ver en SwiftBeta es una mini app. Lo que vamos hacer es realizar una petición HTTP y el resultado lo vamos a mostrar dentro de un Form. Vamos a usar la PokéAPI para sacar un array de Pokemons y los mostraremos en una Form de SwiftUI.

Para que te hagas una idea, el formato del JSON que recibiremos será:

{
  "count": 1118,
  "next": "https://pokeapi.co/api/v2/pokemon?offset=20&limit=20",
  "previous": null,
  "results": [
    {
      "name": "bulbasaur",
      "url": "https://pokeapi.co/api/v2/pokemon/1/"
    },
    {
      "name": "ivysaur",
      "url": "https://pokeapi.co/api/v2/pokemon/2/"
    },
    ...
}

Lo primero de todo que haremos será crear un proyecto de cero. Y crearemos un fichero llamado ViewModel. Dentro de este ViewModel crearemos:

  • PokemonResponseDataModel
  • PokemonDataModel

Vamos a empezar creando el PokemonDataModel:

struct PokemonDataModel: Decodable {
    let name: String
    let url: String
}

Y en el caso de PokemonResponseDataModel caso tendremos una property de tipo Array de PokemonDataModel:

struct PokemonResponseDataModel: Decodable {
    let pokemons: [PokemonDataModel]
    
    enum CodingKeys: String, CodingKey {
        case results
    }
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.pokemons = try container.decode([PokemonDataModel].self, forKey: .results)
    }
}

Lo que hemos hecho ha sido crear el init decoder de forma manual y cuando hemos obtenido el container con las CodingKeys, hemos iterado en la key results para sacar el Array de pokemons.

Dentro de la clase ViewModel, vamos a crear un método llamado getPokemons() que lo que hará será una petición HTTP a la PokéAPI. Para ello debemos especificar una URL (con el endpoint que queremos usar) y usar URLSession para realizar la petición HTTP. El código quedaría de la siguiente manera:

final class ViewModel {
        
    func getPokemons() {
        let url = URL(string: "https://pokeapi.co/api/v2/pokemon/?offset=0&limit=151")
        
        URLSession.shared.dataTask(with: url!) { data, response, error in
            if let _ = error {
                print("Error")
            }
            
            if let data = data,
               let httpResponse = response as? HTTPURLResponse,
               httpResponse.statusCode == 200 {
                let pokemonDataModel = try! JSONDecoder().decode(PokemonResponseDataModel.self, from: data)
                print("Pokemons \(pokemonDataModel)")
            }
            
        }.resume()
    }
}

Fíjate que si todo va bien en nuestra petición HTTP lo que hacemos es printar los pokemons, aquí lo que debemos hacer es guardarlos de alguna manera para que la vista que use este ViewModel sea notificada y así actualizar el listado con los pokemons.

Para hacerlo vamos a conformar el protocolo ObservableObject y vamos a crear una propiedad @Published llamada pokemons, y quedaría así:

final class ViewModel: ObservableObject {
    
    @Published var pokemons: [PokemonDataModel] = []
    
    func getPokemons() {
        let url = URL(string: "https://pokeapi.co/api/v2/pokemon/?offset=0&limit=151")
        
        URLSession.shared.dataTask(with: url!) { data, response, error in
            if let _ = error {
                print("Error")
            }
            
            if let data = data,
               let httpResponse = response as? HTTPURLResponse,
               httpResponse.statusCode == 200 {
                let pokemonDataModel = try! JSONDecoder().decode(PokemonResponseDataModel.self, from: data)
                print("Pokemons \(pokemonDataModel)")
                DispatchQueue.main.async {
                    self.pokemons = pokemonDataModel.pokemons
                }
            }
            
        }.resume()
    }
}

De esta manera, como he dicho antes, cada vez que se actualice esta propiedad la vista se redibujará con los nuevos pokemons.

Ahora hemos acabado con el ViewModel, la siguiente parte es instanciar este ViewModel desde la vista y usarlo. Vamos a ello, nos vamos a ContentView y añadimos el siguiente código:

struct ContentView: View {
    
    @StateObject var viewModel: ViewModel = ViewModel()
    
    var body: some View {
        NavigationView {
            Form {
                ForEach(viewModel.pokemons, id: \.name) { pokemon in
                    Text(pokemon.name)
                }
            }
            .navigationTitle("Pokemons")
        }
    }
}

Si te fijas no está pasando nada, eso es porque no hemos llamado a la función getPokemons(). Para hacerlo, usamos el modificador .onAppear:

struct ContentView: View {
    
    @StateObject var viewModel: ViewModel = ViewModel()
    
    var body: some View {
        NavigationView {
            Form {
                ForEach(viewModel.pokemons, id: \.name) { pokemon in
                    Text(pokemon.name)
                }
            }
            .navigationTitle("Pokemons")
        }.onAppear {
            viewModel.getPokemons()
        }
    }
}

Si ahora compilas la app, verás que hay un delay de unos segundos (mientras se realiza la petición HTTP) y al recibir los datos, se actualiza el listado del Form.

Hoy lo que hemos visto ha sido a cómo usar URLSession con JSONDecoder en Swift en un ejemplo práctico con SwiftUI. Aunque este ejemplo es sencillo, es muy potente para empezar a crear tus propias apps en SwiftUI.