APIのデータを利用する

Published by @SoNiceInfo at 6/24/2020


概要

SwiftUIでWebサーバにあるAPIのデータを使ってアプリを実装する方法を紹介します。
ここでは「モックデータを利用する」でMVVMパターンに則ってモックデータを作っているので変更は最小限で済みます。

大まかな流れ

テレビ番組の一覧を表示するアプリを目指して作ります。 作成は以下の流れで作成します。
フォルダの構成は右のようになります。

モックデータを利用する」と変わるところは2, 3番目です。
  • Modelを作成する(ProgramModel.swift)
  • APIのデータをロードするヘルパーを作る(APIfetch.swift)
  • ViewModelとViewを作成する(ProgramView.swift)
フォルダー階層

Modelを作成する(ProgramModel.swift)

モックデータを利用する」と同じです。 Modelではデータの形を定義します。
struct型は入れ子にすることができます。
TV番組はid, name, summary, image(mediumサイズ)を持ちます。

//
//  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
}

APIのデータを作成する(programs.json)

APIのデータはprograms.jsonで用意しているものとします。(このファイルは今回の検証に限り利用願います。)

[
    {"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"}}
]

APIのデータをロードするヘルパーを作る(APIfetch.swift)

APIのデータをモデルの形にロードするヘルパーを作ります。
今回使うProgram Model以外にも汎用的に利用できると思います。
WebデータはURLSession.shared.dataTaskで取得する事ができます。
DispatchQueue.main.asyncで非同期データを利用できるようにします。
取得したデータはapiFetchを呼び出す際にcompletionを渡すことで利用できます。(感想:JavaScriptのコールバックみたいだなと思いました)

//
//  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()
}

ViewModelとViewを作成する(ProgramView.swift)

ProgramViewModelでヘルパーを使ってデータを呼び出します。
ObservableObjectの使い方については変数を利用するでも紹介しています。
最後にContentView.SwiftProgramView.swiftを呼び出します。

//
//  ProgramView.swift
//

import SwiftUI

class ProgramViewModel: ObservableObject {
    // ViewでScrollViewとForEachを組み合わせて使うと
    // Updateされない現象が発生するので
    // 回避策として初期値に何もないデータを入れています。
    // 参考 -> 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() {
        // 「モックデータを利用する」を見ていただいた方はコメントアウト
        // self.data = dataLoad("programData.json")

        // 今回のapiFetchを呼び出し
        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) {
                // 順番にProgramを処理
                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()
    }
}
APIのデータを使ってテレビ番組を表示する結果

参考

変数を利用する
モックデータを利用する
ios - SwiftUI ForEach not correctly updating in scrollview - Stack Overflow

素材

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


    アプリをリリースしました!

    ToDoアプリ

    iCloudを利用したデバイス間データ共有、ダークモードに対応しています。

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

    IPアドレス履歴保存アプリ

    取得したIPアドレスを確認、位置情報とともに保存できます。

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