Working with REST APIs in SwiftUI: Tips, Tricks, and Best Practices

I am using a REST API to fetch data. You will see how to use URL sessions in Swift to get JSON files and images. 

This is a very important topic, especially if you are planning to land a job as an iOS developer soon.

Using an API in a take-home project is a very common task. 

Overview

  • Introduction
  • URLSession and JSON
  • Fetching images
  • Response and status code
  • Decoding JSON
  • Renaming of properties and CodingKey
  • Parsing different types with init(from decoder)
  • Nested JSON data
  • Fetching more images

Create a URL

let url = URL(string: "https://www.swiftuiio.com")!


Fetching String using URLSession
  • Create and execute a network  request 
  • Create a data task
let url = URL(string: "https://www.swiftuiio.com/")!
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
    if let data = data,   
          let string = String(data: data, encoding: .utf8){
            print(string)    
    }
}
task.resume()
The output will be an HTML response


Fetching Image using URLSession
import Foundation
import UIKit
import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true// write this at the beginning

let url = URL(string: "https://cdn2.thecatapi.com/images/bq1.jpg")!
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
    if let data = data,   
          let image = UIImage(data: data){
            print("success")
            let i = image    
    }
    PlaygroundPage.current.finishExecution()// To finish execution
}
task.resume()


Handling Request Method using URLRequest
import UIKit
import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true

//let url = URL(string: "https://www.swiftuiio.com")!
let url = URL(string: "https://api.thecatapi.com/v1/breeds?limit=1")!

var request = URLRequest(url: url)
request.httpMethod = "GET"
//request.allHTTPHeaderFields = ["x-api-key": "API_key"]

let task = URLSession.shared.dataTask(with: request) { data, response, error in
    
    if let response = response as? HTTPURLResponse, !(200...299).contains(response.statusCode) {
        print(response.statusCode)
        //TODO: throw error
    }
    if let error = error {
        print(error.localizedDescription)
    }
    
    if let data = data,
        let file = String(data: data, encoding: .utf8) {
        print(file)
      
    }
    PlaygroundPage.current.finishExecution()
}

task.resume()
...

How to parse JSON into model objects with Codable
Let us work with network services and APIs, and how to parse JSON into model objects that can be used in your iOS app.
I will demonstrate how to read basic JSON data and how to write custom Swift model types.
Working with Decoding JSON 
//: [Previous](@previous)

import Foundation

/*
 {
   "userId": 1,
   "id": 1,
   "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
   "body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
 }
 */


struct Post: Codable {
    let userId: Int
    let id: Int
    let title: String
    let body: String
}


//let url = URL(string: "https://jsonplaceholder.typicode.com/posts/1")!
let url = URL(string: "https://jsonplaceholder.typicode.com/posts")!


let task = URLSession.shared.dataTask(with: url) { data, response, error in
    
    let decoder = JSONDecoder()
    if let data = data{
        
        do {
           let posts = try decoder.decode([Post].self, from: data)
            print("found \(posts.count) posts")
        } catch {
            print(error)
        }
        
       
    }
    
    
}

task.resume()





//: [Next](@next)
..
How to parse JSON into the model objects with Codable
  • Nested JSON data
  • Renaming of properties and CodingKey
  • Parsing different types with init(from decoder)
  • Fetching more images
Convert JSON data to Swift types

JSON data may not string always, let us see some examples of how to handle different data to swift type

Decoding into custom model objects
..

//: [Previous](@previous)

import Foundation

/*
 [{"weight":{"imperial":"7  -  10","metric":"3 - 5"},"id":"abys","name":"Abyssinian","cfa_url":"http://cfa.org/Breeds/BreedsAB/Abyssinian.aspx","vetstreet_url":"http://www.vetstreet.com/cats/abyssinian","vcahospitals_url":"https://vcahospitals.com/know-your-pet/cat-breeds/abyssinian","temperament":"Active, Energetic, Independent, Intelligent, Gentle","origin":"Egypt","country_codes":"EG","country_code":"EG","description":"The Abyssinian is easy to care for, and a joy to have in your home. They’re affectionate cats and love both people and other animals.","life_span":"14 - 15","indoor":0,"lap":1,"alt_names":"","adaptability":5,"affection_level":5,"child_friendly":3,"dog_friendly":4,"energy_level":5,"grooming":1,"health_issues":2,"intelligence":5,"shedding_level":2,"social_needs":5,"stranger_friendly":5,"vocalisation":1,"experimental":0,"hairless":0,"natural":1,"rare":0,"rex":0,"suppressed_tail":0,"short_legs":0,"wikipedia_url":"https://en.wikipedia.org/wiki/Abyssinian_(cat)","hypoallergenic":0,"reference_image_id":"0XYvRd7oD","image":{"id":"0XYvRd7oD","width":1204,"height":1445,"url":"https://cdn2.thecatapi.com/images/0XYvRd7oD.jpg"}}]

 */


struct Breed: Codable, CustomStringConvertible {
    let id: String
    let name: String
    let temperament: String
    let breedExplaination: String
    let energyLevel: Int
    let isHairless: Bool//for this we need custom init bez it is integer format
    let image: BreedImage
    
    var description: String {
        return "breed with name: \(name) and id \(id), energy level: \(energyLevel) isHairless: \(isHairless ? "YES" : "NO")"
    }
    
    enum CodingKeys: String, CodingKey {
        case id
        case name
        case temperament
        case breedExplaination = "description"
        case energyLevel = "energy_level"
        case isHairless = "hairless"
        case image
    }
    
    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        
        id = try values.decode(String.self, forKey: .id)
        name = try values.decode(String.self, forKey: .name)
        temperament = try values.decode(String.self, forKey: .temperament)
        breedExplaination = try values.decode(String.self, forKey: .breedExplaination)
        energyLevel = try values.decode(Int.self, forKey: .energyLevel)
        
        let hairless = try values.decode(Int.self, forKey: .isHairless)
        isHairless = hairless == 1
        
        image = try values.decode(BreedImage.self, forKey: .image)
    }
    
}

/*
 "image": {
   "height": 1445,
   "id": "0XYvRd7oD",
   "url": "https://cdn2.thecatapi.com/images/0XYvRd7oD.jpg",
   "width": 1204
 },
 */

struct BreedImage: Codable {
    let height: Int
    let id: String
    let url: String
    let width: Int
    
}


let url = URL(string: "https://api.thecatapi.com/v1/breeds?limit=1")!

let task = URLSession.shared.dataTask(with: url) { data , response, error in
    
    let decoder = JSONDecoder()
    if let data = data {
        
        do {
            let breeds = try decoder.decode([Breed].self, from: data)
            print(breeds)
            
        }catch {
            print(error)
        }
    }
}

task.resume()


//: [Next](@next)
..
//: [Previous](@previous)

import Foundation

struct Breed: Codable {
    let id: String
    let name: String
    let temperament: String
}

struct BreedImage: Codable {
    let breeds: [Breed]
    let height: Int
    let id: String
    let url: String
    let width: Int
}


let selectedCatBreedId = "abys"
let url = URL(string: "https://api.thecatapi.com/v1/images/search?limit=9&breed_id=\(selectedCatBreedId)")!

let task = URLSession.shared.dataTask(with: url) { data, response, error in
    
    let decoder = JSONDecoder()
    
    if let data = data {
        
        do {
            let images = try decoder.decode([BreedImage].self, from: data)
            print("success, fetched \(images.count) image urls")
        } catch {
            print(error)
        }
    }
    
    
}

task.resume()


//: [Next](@next)
..
Tips: 
You can also Convert JSON to Data Class online tool.


Comments