Using API Data

Published by @SoNiceInfo at 6/24/2020


Overview

I'll show you how to use data from an API on a web server.
You can implement this with minimum changes because this page is based on Using Mock Data which conform to MVVM pattern.

Guide

We're going to make an app that displays a list of TV programs.
The creation process is as follows
The structure of the folder looks like the one on the right.

The changes from Using Mock Data are second and third.
  • Creating a Model (ProgramModel.swift)
  • Creating a Helper to Load Data from API (APIfetch.swift)
  • Creating a ViewModel and View (ProgramView.swift)
フォルダー階層

Creating a Model (ProgramModel.swift)

This section is the same as Using Mock Data. Model defines the struct of the data.
structs can be nested.
A TV program has id, name, summary and image (medium size).

//
//  ProgramModel.swift
//

import SwiftUI

struct Img: Hashable, Codable {
    var large: String?
    var medium: String?
    var small: String?
}

struct Program: Hashable, Codable {
    let id: Int
    let name: String
    let summary: String
    let image: Img
}

Creating API Data (programs.json)

API Data can be fetched as programs.json. (Please use this file for this verification only.)

[
    {"id": 0, "name": "あるぱか散歩", "summary": "あるぱかさんといっしょに街歩きをしましょう", "image": { "medium": "animal_arupaka"}},
    {"id": 1, "name": "ブタトーク", "summary": "MCぶったさんの独壇場",  "image": { "medium": "animal_buta"}},
    {"id": 2, "name": "とっとこはむはみー", "summary": "はむはみーが大冒険",  "image": { "medium": "animal_hamster"}},
    {"id": 3, "name": "ひよっと", "summary": "ひよっとちゃんがいろんなことに挑戦するよ",  "image": { "medium": "animal_hiyoko"}},
    {"id": 4, "name": "わんさんといっしょ", "summary": "わんさんといっしょに歌ったり踊ったりしよう",  "image": { "medium": "animal_inu"}}
]

Creating a Helper to Load Data from API (APIfetch.swift)

Create a helper to load the API data into the struct of a model.
I think it can be used for other purposes than this program.
Use URLSession.shared.dataTask to fetch Web data.
Use DispatchQueue.main.async to do async proccess.
You can use this data by passing completion when calling apiFetch.

//
//  APIfetch.swift
//

import SwiftUI

func apiFetch<T: Decodable>(_ url: String, completion: @escaping ([T]) -> Void) {
    guard let url = URL(string: url) else { return }

    URLSession.shared.dataTask(with: url) { (data, response, error) in
        guard let data = data else { return }
        let decoder: JSONDecoder = JSONDecoder()
        do {
            let resData = try decoder.decode([T].self, from: data)
            DispatchQueue.main.async {
                completion(resData)
            }
        } catch {
            fatalError("Couldn't load \(url) :\n\(error)")
        }
    }.resume()
}

Creating a ViewModel and View (ProgramView.swift)

Use the helper in ProgramViewModel to fetch data.
Understanding Property Wrappers also show you how to use ObservableObject. Finally, call ProgramView.swift from ContentView.Swift.

//
//  ProgramView.swift
//

import SwiftUI

class ProgramViewModel: ObservableObject {
    // If you use a combination of ScrollView and ForEach in View,
    // The problem is that it is not updated.
    // As a workaround, I've included a blank data in the initial value.
    // Reference -> https://stackoverflow.com/questions/59316078/swiftui-foreach-not-correctly-updating-in-scrollview
    @Published var data: [Program] = [Program(id: 0, name: "", summary: "", image: Img(medium: ""))]

    init() {
        // Call apiFetch this time
        apiFetch("https://d1v1b.com/programs.json"){ resData in
            self.data = resData
        }
    }
}

struct ProgramView: View {
    @ObservedObject var programVM = ProgramViewModel()

    var body: some View {
        ScrollView(.vertical, showsIndicators: false) {
            VStack(spacing: 10) {
                // Process the Program in sequence
                ForEach(programVM.data, id: \.id) { (program) in
                    ZStack(alignment: .bottom) {
                        Image(program.image.medium)
                            .resizable()
                            .scaledToFill()
                            .frame(width: UIScreen.main.bounds.width, height: 300)
                            .background(LinearGradient(gradient: Gradient(colors: [.yellow, .red, .purple]), startPoint: .bottomLeading, endPoint: .topTrailing))
                            .clipShape(RoundedRectangle(cornerRadius: 20))
                        VStack {
                            Text(program.name)
                                .font(.largeTitle)
                                .foregroundColor(.white)
                            Text(program.summary)
                                .foregroundColor(.white)
                        }
                    }
                }
            }
        }
    }
}

struct ProgramView_Previews: PreviewProvider {
    static var previews: some View {
        ProgramView()
    }
}
//
//  ContentView.swift
//

import SwiftUI

struct ContentView: View {

    var body: some View {
        ProgramView()
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
The result of displaying a TV show using API data

References

Using Mock Data
ios - SwiftUI ForEach not correctly updating in scrollview - Stack Overflow

Materials

かわいいフリー素材集 いらすとや


    I released iOS App!

    ToDo App

    Visualize Activity, Data sharing with iCloud, Dark mode supported.

    リリースしたToDoアプリのスクリーンショット

    IP Address bookmark.

    Check and bookamrk IP address of all interfaces with geolocation.

    リリースしたIPアドレス保存アプリのスクリーンショット