diegoRodicio

Está documentación está a túa disposición sin ningún custo económico. Sen embargo, para a súa elaboración dedico moito tempo e recursos, polo que agradecería unha colaboración co que consideres oportuno. Gracias.

View Categories

Cargar JSON

Decodificación de arquivos JSON #

Procesar un arquivo JSON en Swift implica cargar o JSON, definir a estrutura de datos compatible, decodificar os datos e usalos na aplicación.

Vou explicar cada paso con detalle e proporcionar un exemplo práctico.

Pasos para procesar un arquivo JSON en Swift #

  • Definir a estrutura de datos en Swift: Para decodificar JSON, necesitamos unha clase que cumpra co protocolo Decodable. Os nomes das propiedades deben coincidir cos do JSON, pero podemos renomealos usando CodingKeys.
  • Obter os datos JSON: O JSON pode vir de distintas fontes:
    • Desde un arquivo local no proxecto (Bundle.main.url(forResource:)).
    • Desde unha URL remota (URLSession).
    • Desde un String na aplicación (data(using: .utf8)). Este apartado non o vamos a ver.
  • Decodificar o JSON: Usamos JSONDecoder() para converter os datos en obxectos Swift.

UtilidadesJSON.swift #

Todas as funcións que se empregan neste apartado están dentro deste arquivo de utilidades:

//
//  UtilidadesJSON.swift
//  CargarJSONdesdeURL-Novas-IES
//
//  Created by Diego Rodicio Álvarez on 18/2/25.
//

import Foundation

/// Abre un arquivo do proxecto e carga os seus datos.
///
/// Esta función busca un arquivo dentro do `Bundle.main` utilizando o seu nome e extensión.
/// Se o arquivo existe, carga o seu contido como `Data`. En caso contrario, detén a execución con `fatalError()`.
///
/// - Parameter nomeArquivo: Nome do arquivo dentro do proxecto (sen extensión).
/// - Returns: Os datos do arquivo en formato `Data`, ou `nil` se a carga falla.
/// - Warning: Esta función usa `fatalError()`, polo que a aplicación fallará se o arquivo non se atopa ou non se pode cargar.
///
/// - Example:
/// ```swift
/// if let datos = abrirArquivo(nomeArquivo: "datos.json") {
///     print("Arquivo cargado con éxito")
/// }
/// ```
func abrirArquivo(nomeArquivo: String) -> Data? {
    var datos: Data
    
    // Busca o arquivo dentro do bundle da aplicación
    guard let arquivo = Bundle.main.url(forResource: nomeArquivo,
                                  withExtension: nil)
    else {
        fatalError("\(nomeArquivo) non atopado no proxecto.")
    }
     
    do {
        // Intenta cargar os datos do arquivo
        datos = try Data(contentsOf: arquivo)
    } catch {
        fatalError("Non foi posible cargar o arquivo \(nomeArquivo): \(error)")
    }
    return datos
}

/// Descarga e carga os datos dun arquivo desde unha URL.
///
/// Esta función intenta descargar os datos dun arquivo situado nunha URL remota ou local.
/// Se a descarga é exitosa, devolve os datos en formato `Data`. En caso de erro, imprime unha mensaxe e devolve `nil`.
///
/// - Parameter url: A URL do arquivo que se desexa descargar.
/// - Returns: Os datos do arquivo en formato `Data`, ou `nil` se a descarga falla.
///
/// - Example:
/// ```swift
/// if let url = URL(string: "https://exemplo.com/arquivo.json"),
///    let datos = abrirArquivo(url: url) {
///     print("Arquivo descargado con éxito, tamaño: \(datos.count) bytes")
/// }
/// ```
func abrirArquivo(url: URL) -> Data? {
    do {
        // Intenta descargar e cargar os datos do arquivo na URL fornecida
        let data = try Data(contentsOf: url)
        return data
    } catch {
        // Se ocorre un erro, imprímeo e devolve `nil`
        print("Erro ao descargar o arquivo: \(error)")
        return nil
    }
}


/// Decodifica un arquivo JSON nunha estrutura específica.
///
/// Esta función toma datos en formato `Data` e intenta decodificalos nunha estrutura que cumpra o protocolo `Decodable`.
/// Se a decodificación falla, a execución deterase con `fatalError()`.
///
/// - Parameter arquivo: Os datos en formato `Data` que conteñen o JSON.
/// - Returns: Unha instancia de `TipoEstrutura`, que é un tipo xenérico que implementa `Decodable`.
/// - Throws: Non lanza erros explicitamente, pero usa `fatalError()` en caso de fallo.
/// - Warning: Se hai un erro ao procesar o JSON, a aplicación deterase.
/// - Note: Esta función só funciona con tipos que cumpren `Decodable`.
///
/// - Example:
/// ```swift
/// struct Usuario: Decodable {
///     let nome: String
///     let idade: Int
/// }
///
/// let jsonData = """
/// {
///     "nome": "Diego",
///     "idade": 46
/// }
/// """.data(using: .utf8)!
///
/// let usuario: Usuario = procesarJSON(arquivo: jsonData)
/// print(usuario.nome) // "Diego"
/// ```

/*
 📌 Que é TipoEstrutura: Decodable?

 TipoEstrutura é un parámetro de tipo xenérico. Significa que esta función pode traballar con calquera tipo de datos, sempre que cumpra cunha determinada restrición.
     •    TipoEstrutura → É un tipo de datos que será determinado cando se chame á función.
     •    : Decodable → Indica que TipoEstrutura debe cumprir o protocolo Decodable. É dicir, debe ser un tipo que poida ser decodificado desde JSON.
 
 📌 Resumo

 ✅ <TipoEstrutura: Decodable> fai que a función sexa xerérica, podendo aceptar calquera tipo que cumpra Decodable.
 ✅ Permite reutilizar a mesma función para diferentes estruturas de datos sen escribir código duplicado.
 ✅ Evita o uso de tipos específicos e fai que o código sexa máis flexible e escalable. 🚀
 
*/
func procesarJSON<TipoEstrutura: Decodable>(arquivo: Data) -> TipoEstrutura {
    
    do {
        // Intenta decodificar os datos JSON na estrutura fornecida
        return try JSONDecoder().decode(TipoEstrutura.self, from: arquivo)
    } catch {
        // Se ocorre un erro na decodificación, a aplicación deterase
        fatalError("Non foi posible parsear o arquivo: \(error)")
    }
}

Exemplo: Procesar un JSON dun arquivo local ou URL #

Exemplo de JSON (usuarios.json) #

{
    "usuarios": [
        {
            "id": 1,
            "nome": "Ana",
            "idade": 30,
            "correo": "ana@example.com"
        },
        {
            "id": 2,
            "nome": "Carlos",
            "idade": 25,
            "correo": "carlos@example.com"
        }
    ]
} 

Paso 1: Crear a estrutura de datos en Swift #

Definimos as estruturas que se correspondan co JSON. Empregamos:

  • Decodable permite que JSONDecoder converta o JSON en estas estruturas.
  • Identifiable permite que SwiftUI os use en vistas de listas (List).
  • Os CodingKeys permiten cambiar os nomes das propiedades para que coincidan co JSON. Se o JSON cambia os nomes das claves, só hai que modificar CodingKeys sen tocar o resto do código.
import Foundation

// Estrutura para un usuario individual
struct UsuarioDataModel: Decodable, Identifiable {
    let id: Int
    let nome: String
    let idade: Int
    let email: String
    
    // Mapeo de chaves JSON -> Propiedades da clase
    enum CodingKeys: String, CodingKey {
        case id
        case nome
        case idade
        case email = "correo" // O JSON usa "correo", pero na clase de Swift usamos "email"
    }
}

// Estrutura para o conxunto de usuarios
struct UsuariosDataModel: Decodable {
    let items: [UsuarioDataModel]
    
    // Mapeo de chaves JSON -> Propiedades da clase
    enum CodingKeys: String, CodingKey {
        case items = "usuarios"
    }
}

Paso 2a: Función para abrir o arquivo JSON no caso de que estea dentro do proxecto #

Se o JSON está dentro do proxecto, cargámolo con Bundle.main.url(forResource:)

/// Abre un arquivo do proxecto e carga os seus datos.
///
/// Esta función busca un arquivo dentro do `Bundle.main` utilizando o seu nome e extensión.
/// Se o arquivo existe, carga o seu contido como `Data`. En caso contrario, detén a execución con `fatalError()`.
///
/// - Parameter nomeArquivo: Nome do arquivo dentro do proxecto (sen extensión).
/// - Returns: Os datos do arquivo en formato `Data`, ou `nil` se a carga falla.
/// - Warning: Esta función usa `fatalError()`, polo que a aplicación fallará se o arquivo non se atopa ou non se pode cargar.
///
/// - Example:
/// ```swift
/// if let datos = abrirArquivo(nomeArquivo: "datos.json") {
///     print("Arquivo cargado con éxito")
/// }
/// ```
func abrirArquivo(nomeArquivo: String) -> Data? {
    var datos: Data
    
    // Busca o arquivo dentro do bundle da aplicación
    guard let arquivo = Bundle.main.url(forResource: nomeArquivo,
                                  withExtension: nil)
    else {
        fatalError("\(nomeArquivo) non atopado no proxecto.")
    }
     
    do {
        // Intenta cargar os datos do arquivo
        datos = try Data(contentsOf: arquivo)
    } catch {
        fatalError("Non foi posible cargar o arquivo \(nomeArquivo): \(error)")
    }
    return datos
}

Paso 2b: Función para abrir o arquivo JSON a partir dunha URL #

/// Descarga e carga os datos dun arquivo desde unha URL.
///
/// Esta función intenta descargar os datos dun arquivo situado nunha URL remota ou local.
/// Se a descarga é exitosa, devolve os datos en formato `Data`. En caso de erro, imprime unha mensaxe e devolve `nil`.
///
/// - Parameter url: A URL do arquivo que se desexa descargar.
/// - Returns: Os datos do arquivo en formato `Data`, ou `nil` se a descarga falla.
///
/// - Example:
/// ```swift
/// if let url = URL(string: "https://exemplo.com/arquivo.json"),
///    let datos = abrirArquivo(url: url) {
///     print("Arquivo descargado con éxito, tamaño: \(datos.count) bytes")
/// }
/// ```
func abrirArquivo(url: URL) -> Data? {
    do {
        // Intenta descargar e cargar os datos do arquivo na URL fornecida
        let data = try Data(contentsOf: url)
        return data
    } catch {
        // Se ocorre un erro, imprímeo e devolve `nil`
        print("Erro ao descargar o arquivo: \(error)")
        return nil
    }
}

Paso 3: Función para decodificar o JSON #

/// Decodifica un arquivo JSON nunha estrutura específica.
///
/// Esta función toma datos en formato `Data` e intenta decodificalos nunha estrutura que cumpra o protocolo `Decodable`.
/// Se a decodificación falla, a execución deterase con `fatalError()`.
///
/// - Parameter arquivo: Os datos en formato `Data` que conteñen o JSON.
/// - Returns: Unha instancia de `TipoEstrutura`, que é un tipo xenérico que implementa `Decodable`.
/// - Throws: Non lanza erros explicitamente, pero usa `fatalError()` en caso de fallo.
/// - Warning: Se hai un erro ao procesar o JSON, a aplicación deterase.
/// - Note: Esta función só funciona con tipos que cumpren `Decodable`.
///
/// - Example:
/// ```swift
/// struct Usuario: Decodable {
///     let nome: String
///     let idade: Int
/// }
///
/// let jsonData = """
/// {
///     "nome": "Diego",
///     "idade": 46
/// }
/// """.data(using: .utf8)!
///
/// let usuario: Usuario = procesarJSON(arquivo: jsonData)
/// print(usuario.nome) // "Diego"
/// ```

/*
 📌 Que é TipoEstrutura: Decodable?

 TipoEstrutura é un parámetro de tipo xenérico. Significa que esta función pode traballar con calquera tipo de datos, sempre que cumpra cunha determinada restrición.
     •    TipoEstrutura → É un tipo de datos que será determinado cando se chame á función.
     •    : Decodable → Indica que TipoEstrutura debe cumprir o protocolo Decodable. É dicir, debe ser un tipo que poida ser decodificado desde JSON.
 
 📌 Resumo

 ✅ <TipoEstrutura: Decodable> fai que a función sexa xerérica, podendo aceptar calquera tipo que cumpra Decodable.
 ✅ Permite reutilizar a mesma función para diferentes estruturas de datos sen escribir código duplicado.
 ✅ Evita o uso de tipos específicos e fai que o código sexa máis flexible e escalable. 🚀
 
*/
func procesarJSON<TipoEstrutura: Decodable>(arquivo: Data) -> TipoEstrutura {
    
    do {
        // Intenta decodificar os datos JSON na estrutura fornecida
        return try JSONDecoder().decode(TipoEstrutura.self, from: arquivo)
    } catch {
        // Se ocorre un erro na decodificación, a aplicación deterase
        fatalError("Non foi posible parsear o arquivo: \(error)")
    }
}

Paso 4: Usar as funcións anteriores #

if let datos = abrirArquivo(nomeArquivo: "usuarios") {
    if let usuarios: UsuariosDataModel = procesarJSON(arquivo: datos) {
        for usuario in usuarios.items {
            print("Usuario: \(usuario.nome), Email: \(usuario.email)")
        }
    }
}

Exemplo cargar un arquivo JSON #

Neste apartado veremos outro exemplo para cargar un arquivo JSON e traducir as entradas nun array de obxectos.

Supoñamos que temos o seguinte arquivo JSON:

[
    {
        "id": "aa32jj887hhg55",
        "nome": "Tesla Model 3",
        "descricion": "Coche totalmente eléctrico de 4 portas. Autonomía 500 km. 0-100 km/h 3.2 segundos.",
        "hibrido": false,
        "nomeImaxe": "tesla_model_3"
    },
    {
        "id": "bb32jj887hhg77",
        "nome": "Tesla Model S",
        "descricion": "Coche eléctrico de 5 portas. O modelo Performance ten varios motores eléctrico que acadan 1020 CV. 0-100 km/h 2.1 segundos.",
        "hibrido": false,
        "nomeImaxe": "tesla_model_s"
    }
]

Paso 1 de 5: Crear a estrutura de datos #

Necesitamos unha ou varias estruturas que teñan, como propiedades, as que se correspondan coas chaves do arquivo JSON.

  • A estrutura CocheDataModel que vamos a crear, ten que implementar os protocolos Codable e Identifiable, para poder cargar o JSON e empregar a vista de List.
  • Dentro da estrutura definimos unha enumeración ‌enum CodingKeys: String, CodingKey (sempre ten que ter ese nome), de xeito que cada caso da enumeración se corresponderá cunha propiedade da estrutura, e, si o nome desa propiedade non coincide coa chave do JSON, se iguala ao nome da chave.
    • Así, no exemplo, a propiedade imaxe non coincide co nome da chave no JSON (nomeImaxe)
    • O nome do resto de propiedades si coincide co nome das chaves no JSON

Así, supondo o JSON do exemplo, teremos o seguinte código onde implementamos modelo de datos na clase CocheDataModel:

struct CocheDataModel: Codable, Identifiable {
    var id: String
    var nome: String
    var descricion: String
    var hibrido: Bool
    var imaxe: String
    
    // Mapeo de chaves JSON -> Propiedades da clase
    enum CodingKeys: String, CodingKey {
        case id
        case nome
        case descricion
        case hibrido
        case imaxe = "nomeImaxe" // O JSON usa "nomeImaxe", pero na clase de Swift usamos "imaxe"
    }
}

Paso 2 de 5: Abrir o arquivo JSON #

Empregaremos unha función de que lea un arquivo JSON (pasándolle o nome do arquivo) e devolva os datos do arquivo:

func abrirArquivo(nomeArquivo: String) -> Data? {
    var datos: Data
    
    // Busca o arquivo dentro do bundle da aplicación
    guard let arquivo = Bundle.main.url(forResource: nomeArquivo,
                                  withExtension: nil)
    else {
        fatalError("\(nomeArquivo) non atopado no proxecto.")
    }
     
    do {
        // Intenta cargar os datos do arquivo
        datos = try Data(contentsOf: arquivo)
    } catch {
        fatalError("Non foi posible cargar o arquivo \(nomeArquivo): \(error)")
    }
    return datos
}

Paso 3 de 5: Decodificar o JSON #

A partir do arquivo JSON leído no anterior apartado, devolve un array de obxectos que teña as propiedades que se correspondan coas claves do arquivo JSON.

func procesarJSON<TipoEstrutura: Decodable>(arquivo: Data) -> TipoEstrutura {
    
    do {
        // Intenta decodificar os datos JSON na estrutura fornecida
        return try JSONDecoder().decode(TipoEstrutura.self, from: arquivo)
    } catch {
        // Se ocorre un erro na decodificación, a aplicación deterase
        fatalError("Non foi posible parsear o arquivo: \(error)")
    }
}

Paso 4 de 5: Almacenar os datos obtidos do JSON #

Por último,empregando as funcións dos pasos 2 e 3, vamos a almacenar os datos cargados no modelo creado no primeiro paso.

Para decodificar e almacenar os datos, utilizaremos unha clase que implementa o protocolo ObservableObject .

Dentro da clase, teremos unha propiedade onde almacenaremos os datos (empregando unha estrutura creada no modelo de datos) que imos a empregar no noso deseño de interfaz mediante SwiftUI. É por iso que necesitamos implementar o protocolo ObservableObject e imos empregar o property wrapper @Published .

Desta maneira, cada vez que se actualice esta propiedade, a vista que utilizaremos en SwifUI se redebuxará coas novas descargadas.

No caso do exemplo, a clase a chamaremos AlmacenCoches. Como comentei:

  • Teremos unha propiedade Published onde almacenaremos os datos empregando o modelo de datos programado no primeiro punto.
  • Queremos almacenar un array de coches, polo tanto, no caso do exemplo, a propiedade será: @Published var coches: [CocheDataModel]

Así, no exemplo anterior (tendo en conta que o arquivo JSON está almacenado noso proxecto co nome datosCoches.json):

final class AlmacenCoches : ObservableObject {
    @Published var coches: [CocheDataModel]
    
    init () {
        coches = procesarJSON(arquivo: abrirArquivo(nomeArquivo: "datosCoches.json")!)
    }
}

Paso 5 de 5: Interface en SwiftUI #

Exemplo de interface empregando o modelo dos pasos anteriores:

struct VistaPrincipal: View {
    
    @StateObject var almacenCoches: AlmacenCoches = AlmacenCoches()
    
    var body: some View {
        List {
            ForEach(almacenCoches.coches){ coche in
                CeldaLista(coche: coche)
            }
        }
    }
}


struct CeldaLista: View {
    
    var coche: CocheDataModel

    var body: some View {
        HStack {
            Image(coche.imaxe)
                .resizable()
                .aspectRatio(contentMode: .fit)
                .frame(width: 100, height: 60)
            Text(coche.nome)
        }
    }
}

Cargar JSON desde unha URL #

A continuación veremos como cargar un arquivo JSON dende unha URL e, posteriormente, tal e como faciamos no punto anterior, transformar o JSON descargado a un modelo da nosa app.

Para realizar esta tarefa suporemos un caso real. A través da url https://www.iessanmamede.com/category/novas/feed/json obteremos todas as novas da web do IES San Mamede en formato JSON.

Se descargamos este arquivo no navegador, observamos que o JSON ten o seguinte formato:

{
    "version": "https://jsonfeed.org/version/1.1",
    "user_comment": "This feed allows you to read the posts from this site in any feed reader that supports the JSON Feed format....",
    "next_url": "https://www.iessanmamede.com/category/novas/feed/json?paged=2",
    "home_page_url": "https://www.iessanmamede.com/category/novas",
    "feed_url": "https://www.iessanmamede.com/category/novas/feed/json",
    "language": "gl-ES",
    "title": "Novas arquivos - IES San Mamede",
    "description": "P\u00e1xina Oficial do Instituto de Educaci\u00f3n Secundaria IES San Mamede (Maceda) Ourense",
    "icon": "https://www.iessanmamede.com/wp-content/uploads/2023/08/logo_sm_150.jpg",
    "items": [
        {
            "id": "https://www.iessanmamede.com/?p=2159",
            "url": "https://www.iessanmamede.com/proxecto-emprende-sorteo-ipad/",
            "title": "Proxecto eMprende \u2013 Sorteo iPad",
            "content_html": "Contido da nova en html",
            "date_published": "2024-01-24T16:27:12+01:00",
            "date_modified": "2024-01-24T18:30:40+01:00",
            "authors": [
                {
                    "name": "drodicio",
                    "url": "https://www.iessanmamede.com/author/drodicio/",
                    "avatar": "https://secure.gravatar.com/avatar/e6301037117d1756e160e4853dd3060e?s=512&amp;d=mm&amp;r=g"
                }
            ],
            "author": {
                "name": "drodicio",
                "url": "https://www.iessanmamede.com/author/drodicio/",
                "avatar": "https://secure.gravatar.com/avatar/e6301037117d1756e160e4853dd3060e?s=512&amp;d=mm&amp;r=g"
            },
            "image": "https://www.iessanmamede.com/wp-content/uploads/2024/01/IMG_0403_modificado-scaled.jpg",
            "tags": [
                "Ultimas Novas"
            ],
            "summary": "No marco do proxecto eMprende, a semana pasada tivo lugar o proceso de entrega dun iPad ao alumno agraciado no sorteo, Alejandro Gil..."
        },
        {
            "id": "https://www.iessanmamede.com/?p=1957",
            "url": "https://www.iessanmamede.com/java-magician/",
            "title": "Java Magician",
            "content_html": "Contido da nova en html",
            "date_published": "2023-11-06T12:06:53+01:00",
            "date_modified": "2023-11-06T12:19:45+01:00",
            "authors": [
                {
                    "name": "drodicio",
                    "url": "https://www.iessanmamede.com/author/drodicio/",
                    "avatar": "https://secure.gravatar.com/avatar/e6301037117d1756e160e4853dd3060e?s=512&amp;d=mm&amp;r=g"
                }
            ],
            "author": {
                "name": "drodicio",
                "url": "https://www.iessanmamede.com/author/drodicio/",
                "avatar": "https://secure.gravatar.com/avatar/e6301037117d1756e160e4853dd3060e?s=512&amp;d=mm&amp;r=g"
            },
            "image": "https://www.iessanmamede.com/wp-content/uploads/2023/11/java-scaled.webp",
            "tags": [
                "dam",
                "fp",
                "Inform\u00e1tica",
                "java",
                "programaci\u00f3n"
            ],
            "summary": "En Java Magician podes atopar diversos tutoriais de programaci\u00f3n en Java."
        }
    ]
}

Vamos a quitar do JSON aqueles datos que non vamos a empregar na aplicación, de xeito que so nos interesará esta parte:

{
"home_page_url": "https://www.iessanmamede.com/category/novas",
"icon": "https://www.iessanmamede.com/wp-content/uploads/2023/08/logo_sm_150.jpg",
"items": [
        {
            "id": "https://www.iessanmamede.com/?p=2159",
            "url": "https://www.iessanmamede.com/proxecto-emprende-sorteo-ipad/",
            "title": "Proxecto eMprende \u2013 Sorteo iPad",
            "image": "https://www.iessanmamede.com/wp-content/uploads/2024/01/IMG_0403_modificado-scaled.jpg",
            "summary": "No marco do proxecto eMprende, a semana pasada tivo lugar o proceso de entrega dun iPad ao alumno agraciado no sorteo, Alejandro Gil..."
        },
        {
            ...
        },
        {
            ...
        }
]
...
}

Os datos que nos interesan son:

  • A chave items, de xeito que será un array, onde cada elemento do array será unha nova da que teremos as seguintes chaves:
    • id: O identificador da nova
    • url: A dirección url da nova
    • title: O título da nova
    • image: A dirección url da imaxe de portada da nova
    • summary: O resumo da nova

Paso 1 de 5: Crear a estrutura de datos #

Neste punto teremos que programar o modelo de datos que me permitirá almacenar e xestionar os datos procedentes do JSON.

Necesitaremos crear as estruturas necesarias para procesar:

  • A chave items, de xeito que será un array, onde cada elemento do array será unha nova da que teremos as seguintes chaves:
    • id: O identificador da nova
    • url: A dirección url da nova
    • title: O título da nova
    • image: A dirección url da imaxe de portada da nova
    • summary: O resumo da nova

Necesitamos as clases/estruturas que teñan, como propiedades, as que se correspondan coas chaves do arquivo JSON. Nesta ocasión, necesitaremos crear dúas clases

  • A clase correspondente coa chave items, que tal e como menciono será un array de novas.
  • A clase que se corresponde con unha nova

1.1: Creación do modelo de datos para unha nova (NovaDataModel) #

  • A estrutura NovaDataModel que vamos a crear, ten que implementar os protocolos Codable e Identifiable, para poder cargar o JSON e empregar a vista de List.
  • As propiedades da estrutura que vamos a crear coinciden coas chaves do arquivo JSON que nos interesan. Non é obrigatorio incluír como propiedades todas as chaves do JSON.

Dentro da estrutura definimos unha enumeración ‌enum CodingKeys: String, CodingKey (sempre ten que ter ese nome), de xeito que cada caso da enumeración se corresponderá con unha propiedade da estrutura, e, si o nome desa propiedade non coincide coa chave do JSON, se iguala ao nome da chave.

Así, teremos, por exemplo:

  struct NovaDataModel: Codable, Identifiable {
    var id: String
    var url: String
    var titulo: String
    var descricion: String
    var imaxe: String
    
    // Mapeo de chaves JSON -> Propiedades da clase
    enum CodingKeys: String, CodingKey {
        case id
        case url
        case titulo = "title" // O JSON usa "title", pero na clase de Swift usamos "titulo"
        case descricion = "summary" // O JSON usa "summary", pero na clase de Swift usamos "descricion"
        case imaxe = "image" // O JSON usa "image", pero na clase de Swift usamos "imaxe"
    }
}

No exemplo vemos que:

  • A propiedades titulo se corresponde coa chave title do JSON
  • A propiedades descricion se corresponde coa chave summary do JSON
  • A propiedades imaxe se corresponde coa chave image do JSON
  • O resto de propiedades coinciden en nome coas chaves do JSON

1.2: Creación do modelo de datos para as novas (NovasDataModel) #

Como comentamos máis arriba, as novas do IES San Mamede se corresponden coas chaves de primeiro nivel do JSON. Non vamos a procesar todas as chaves, concretamente:

  • home_page_url
  • icon
  • items. Fixádevos que items é un array de novas que procesamos mediante NovaDataModel – paso 1.1

Así:

struct NovasDataModel: Codable {
    var url: String
    var icono: String
    var novas: [NovaDataModel]
    
    enum CodingKeys: String, CodingKey {
        case url = "home_page_url" // O JSON usa "home_page_url", pero na clase de Swift usamos "url"
        case icono = "icon" // O JSON usa "icon", pero na clase de Swift usamos "icono"
        case novas = "items" // O JSON usa "items", pero na clase de Swift usamos "novas"
    }
}

Paso 2 de 5: Obter o arquivo JSON #

Empregaremos unha función de que descargue un arquivo JSON (pasándolle a url) e devolva os datos do arquivo:

func abrirArquivo(url: URL) -> Data? {
    do {
        // Intenta descargar e cargar os datos do arquivo na URL fornecida
        let data = try Data(contentsOf: url)
        return data
    } catch {
        // Se ocorre un erro, imprímeo e devolve `nil`
        print("Erro ao descargar o arquivo: \(error)")
        return nil
    }
}

Paso 3 de 5: Decodificar o JSON #

A partir do arquivo JSON descargado no anterior apartado, devolve un array de obxectos que teña as propiedades que se correspondan coas claves do arquivo JSON.

func procesarJSON<TipoEstrutura: Decodable>(arquivo: Data) -> TipoEstrutura {
    
    do {
        // Intenta decodificar os datos JSON na estrutura fornecida
        return try JSONDecoder().decode(TipoEstrutura.self, from: arquivo)
    } catch {
        // Se ocorre un erro na decodificación, a aplicación deterase
        fatalError("Non foi posible parsear o arquivo: \(error)")
    }
}

Paso 4 de 5: Almacenar os datos obtidos do JSON #

Por último,empregando as funcións dos pasos 2 e 3, vamos a almacenar os datos cargados no modelo creado no primeiro paso.

Para decodificar e almacenar os datos, utilizaremos unha clase que implementa o protocolo ObservableObject .

Dentro da clase, teremos unha propiedade onde almacenaremos os datos (empregando unha estrutura creada no modelo de datos) que imos a empregar no noso deseño de interfaz mediante SwiftUI. É por iso que necesitamos implementar o protocolo ObservableObject e imos empregar o property wrapper @Published .

Desta maneira, cada vez que se actualice esta propiedade, a vista que utilizaremos en SwifUI se redebuxará coas novas descargadas.

No caso do exemplo, a clase a chamaremos AlmacenNovas. Como comentei:

  • Teremos unha propiedade Published onde almacenaremos os datos empregando o modelo de datos programado no primeiro punto.

  • Queremos almacenar as novas, polo tanto, no caso do exemplo, a propiedade será: @Published var item: NovasDataModel. Lembra, que en NovasDataModel hay un propiedade chamadas NovasDataModel.novas que é donde se atopará o array de novas.


    Así:


final class AlmacenNovas: ObservableObject {
    
    @Published var item: NovasDataModel
    let url = URL(string: "https://www.iessanmamede.com/category/novas/feed/json")
    
    init() {
        item = procesarJSON(arquivo: abrirArquivo(url: url!)!)
        
    }
}

Paso 5 de 5: Interface en SwiftUI #

Exemplo de interface empregando o modelo dos pasos anteriores:

struct NovasView: View {
    
    @StateObject var almacenNovas: AlmacenNovas = AlmacenNovas()
    
    var body: some View {
        NavigationView {
            List(almacenNovas.item.novas) {
                nova in
                Link(destination: URL(string: nova.url)!) {
                    
                    HStack {
                        ImaxeNovaView(url: nova.imaxe)
                        
                        VStack(alignment: .leading) {
                            TituloNovaView(titulo: nova.titulo)
                                .foregroundStyle(.black)
                            ResumoNovaView(resumo: nova.descricion)
                                .foregroundStyle(.gray)
                        }
                    }
                }
            }
            .navigationTitle("Novas IES San Mamede")
        }
    }
}

struct ImaxeNovaView: View {
    var url: String
    
    var body: some View {
        AsyncImage(url: URL(string: url)) { image in
            image
                .resizable()
                .scaledToFill()
                .frame(width: 80, height: 80)
                .cornerRadius(10)

        } placeholder: {
            ProgressView()
        }
    }
}

struct TituloNovaView: View {
    var titulo: String
    
    var body: some View {
        Text(titulo).font(.title3).lineLimit(2).padding(.bottom, 3)
    }
}

struct ResumoNovaView: View {
    
    var resumo: String
    var body: some View {
        Text(resumo).font(.subheadline).lineLimit(2)
    }
}