Requisitando dados de uma API com SwiftUI

Gabriel Thomaz
5 min readSep 8, 2023

--

Uma das maneiras de exercitar o conhecimento em alguma linguagem é criando uma aplicação utilizando dados externos. Dessa forma, é possível definir um tema e trabalhar em um projeto sem se preocupar com criação dos dados.

Aqui, será visto uma das maneiras de buscar dados de um API e utilizá-los com Swift, a linguagem oficial da Apple, com intuito de facilitar o entendimento desse processo que muitas vezes parece complexo.

MODEL

O primeiro passo é escolher uma API na qual se sinta confortável em desenvolver toda a aplicação em volta dos dados. Visto isso, é necessário analisar no arquivo a possibilidade de definir todos os dados ou somente alguns, já que essa escolha pode começar a definir como será estruturado o arquivo Model no seu projeto.

struct Personagem: Codable, Identifiable {
let id : String
let name : String?
let surname : String?
let image: String?
let age : String?
}

O intuito de saber se será utilizado todos os dados da API ou não está atrelado ao protocolo que a struct receberá:

Decodable — Mapeia parte da API / Pega da URL e traz pro array. Permite a conversão do arquivo JSON em algum tipo de dado do Swift.

Encodable — Pega um dado do array e envia pra API, ou seja, converte um tipo de dado do Swift em alguma representação como o JSON.

Codable — É a soma dos dois anteriores. Aceita somente buscar todos os dados da API.

ViewModel

class ViewModel : ObservableObject {
@Published var persona : [Personagem] = []

No arquivo que busca as informações da API, ou seja, a ViewModel será necessário a criação de uma classe do tipo ObservableObject, que permite criar objetos que podem ser observados por views para que estas views possam ser atualizadas automaticamente sempre que os dados dentro do objeto mudarem.

Um array criado com o wrapper @Published que é do tipo da struct da Model é inicializado com um array vazio. Esse wrapper é importante, pois indica que qualquer view que esteja observando a propriedade "persona" será notificada quando ela mudar.

A função fetch basicamente pega a URL, cria uma seção, executa uma tarefa, decodifica o modelo Personagem e adiciona no array "persona".

Como?

A função inicia com o intuito de testar se a URL é valida. Assim, é criado com "guard let" a constante "url" convertendo a string para URL. Se a conversão falhar, a constante ficará vazia e retornará desse ponto.

func fetch(){
guard let url = URL(string: "URL_da_API" ) else{
return
}

O aplicativo tem uma fila de etapas para serem executadas e como depende-se de um servidor externo não se sabe o tempo de resposta nem se os dados voltarão corretamente, ou até se voltarão. Dessa forma, é criado uma task que coloca na fila de afazeres do app o processamento dos dados da API. A partir do momento que os dados forem processados a função armazena as informações em "persona".

O "dataTask" é parte do mecanismo de rede assíncrona fornecido pela "URLSession" que devolve sempre 3 variáveis: data, response e error, isto é, os dados que serão utilizados, o response (_) que são metadados, por exemplo, se o servidor tem autenticação, o IP do servidor, quando foi enviado, etc. Como não será necessário os dados do response, é utilizado um underline com intuito de otimizar o processamento do código. O Swift presume que é uma variável, mas que não será utilizada. E por último, o error, que recebe valor se houver falha no mapeamento.

A constante "task" tem um "guard let data" que anuncia que se "data" possuir valor, o mesmo é atribuído, mas também é necessário que erro seja nulo. Ou seja, só é utilizado o dado se não houver erro. O "return" finaliza a função se "error" receber algum valor

let task = URLSession.shared.dataTask(with: url){ [weak self] data, _, error in
guard let data = data, error == nil else{
return
}

Ainda em mecanismos de segurança, é necessário decodificar o JSON através da struct "Personagem", isto é, coletar as informações que vieram em texto e converter pro objeto da struct.

Como exemplo, a API pode estar devolvendo índices, isso significa que temos vários arrays. Então "Personagem" precisa estar como array no parsed. Se o JSON começasse com chaves não seria necessário.

Mais uma vez, o "parsed" também é um mecanismo de segurança para a função. Se der algum erro, ficará salvo nessa constante e o array continuará vazio, ou seja, não haverá disparo de informação pra View.

Abaixo do parsed, se a informação chegou sem nenhum erro, é disparada para a fila de processos de aplicativo e o dado que está na constante é atribuído em "persona", que é a informação que a tela no app está esperando. Se o "try" falhar, a validação cai no "Catch" e imprime um erro.

do {
let parsed = try JSONDecoder().decode([Personagem].self, from: data)

DispatchQueue.main.async {
self?.persona = parsed
}
}catch{
print(error)
}

Ao final, em “task.resume()” a tarefa é executada.

class ViewModel : ObservableObject {
@Published var persona : [Personagem] = []

func fetch(){
guard let url = URL(string: "URL_da_API" ) else{
return
}

let task = URLSession.shared.dataTask(with: url){ [weak self] data, _, error in
guard let data = data, error == nil else{
return
}

do {
let parsed = try JSONDecoder().decode([Personagem].self, from: data)

DispatchQueue.main.async {
self?.persona = parsed
}
}catch{
print(error)
}
}

task.resume()
}
}

Resumo da função:

  • Verifica se a URL é valida;
  • Define quais variáveis serão recebidas;
  • Verifica se tem dado;
  • Converte o dado para array;
  • Atribui o array do dado no array da classe.

A tela já recebeu os dados necessários para processar?

Não.

View

Por último, na View, é necessário instanciar a ViewModel para receber o que vem dela. O wrapper @StateObject será responsável por atualizar a tela toda vez que a variável receber um valor.

 @StateObject var viewModel = ViewModel()

É essencial chamar a função "fetch" na View. Pode ser feito através do .onAppear(), que normalmente é utilizado na chave que engloba todas as views, por exemplo uma NavigationStack. Basicamente, quando a tela começar a ser processada a função é disparada, assim dizendo, enquanto a View é criada, os dados já estão sendo buscados.

    .onAppear(){
viewModel.fetch()
}

Agora sim!

Para visualizar os dados na View é possível percorrer pelo array utilizando um “ForEach(viewModel.persona)” e recorrendo aos dados da struct utilizando o index do ForEach, por exemplo:

ForEach(viewModel.persona){ index in
Text(index.name!)
}

--

--

Gabriel Thomaz

Engenheiro Civil formado e estudante de Sistemas de Informação - CEFET/RJ. Atuo como instrutor no projeto de capacitação profissional HACKATRUCK.