TechSwiftUIJapanese

[SwiftUI]Web上のAPIからJSONデータを取得するサンプルコード

Tech

Xcode 12.4, Swift 5

SwiftUIを用いて、Web上で稼働しているAPIにアクセスしてJSON形式のデータを取得し、画面上に表示するサンプルコードを作成してみました。利用するAPIやデータ、表示するViewなども極力シンプルな形にしています。

API & Data

[ads]

今回は、以下のサイトで公開されているAPIを使わせていただくことにします。Todo管理ツールを想定したデータとなっていて、ユーザID、ID、タイトル、完了フラグがセットとなったデータが複数格納されています。

JSONPlaceholder - Free Fake REST API

今回は、最もシンプルなデータを利用することにしますので、以下の形式でAPI実行して取得できるデータを対象にします。

https://jsonplaceholder.typicode.com/todos/1
{
	"userId": 1,
	"id": 1,
	"title": "delectus aut autem",
	"completed": false
}

Implementaion

[ads]

データ取得用のViewの作成

まずは、データを取得するViewを作成していきます。以下がポイントになります。

  • 取得するデータ型(ToDoData)をJSONで取得する要素のそれぞれの型に合わせ(Int, Int, String, Bool)、Codableで定義
  • 取得するデータ(todo)を@Stateで定義し、値の動的な変化に対応させる
import SwiftUI

struct ApiView: View {
    @State private var todo: TodoData = TodoData(userId: -1, id: -1, title: "na", completed: false)
    
    struct TodoData: Codable {
        var userId: Int
        var id: Int
        var title: String
        var completed: Bool
    }
    
    var body: some View {
        VStack(spacing: 10) {
            Text("Get Data")
                .padding(10)
                .background(Color(.green))
                .cornerRadius(20)
            Text(todo.title)
        }
    }
    
}

この状態のViewは以下のようになります。

GetDataボタンをタップすることで、その下の領域に取得したデータからタイトルを表示するようにしたいと思います。現状は、初期値である”na”が表示されています。

データ取得部分の作成

まずは、GetDataボタンをタップする部分を追加していきます。タップしたら、getData()を呼び出すかたちです。

struct ApiView: View {
    @State private var todo: TodoData = TodoData(userId: -1, id: -1, title: "na", completed: false)
    
    struct TodoData: Codable {
        var userId: Int
        var id: Int
        var title: String
        var completed: Bool
    }
    
    var body: some View {
        VStack(spacing: 10) {
            Text("Get Data")
                .padding(10)
                .background(Color(.green))
                .cornerRadius(20)
                .onTapGesture {
                    getData()
                }
            Text(todo.title)
        }
    }
    
    func getData() {
    }
}

次に、getData()の中身を作成していきます。

    func getData() {
        guard let url = URL(string: "https://jsonplaceholder.typicode.com/todos/1") else { return }
        URLSession.shared.dataTask(with: url) {(data, response, error) in
            do {
                if let todoData = data {
                    let decodedData = try JSONDecoder().decode(TodoData.self, from: todoData)
                    self.todo = decodedData
                } else {
                    print("No data", data as Any)
                }
            } catch {
                print("Error", error)
            }
        }.resume()
    }

getDataの内容について、いくつかポイント(ハイライト部分)を説明します。

URLSession, Task

URLSession.shared.dataTask(with: url) {(data, response, error) in
...
}

今回はシンプルな通信なので、shared sessionを使い、data taskを用いることにしているため上記の実装としています。

URLSession | Apple Developer Documentation
An object that coordinates a group of related, network data transfer tasks.

JSONDecoder

let decodedData = try JSONDecoder().decode(TodoData.self, from: todoData)

JSONDecoder.decodeにJSONのデータ型であるTodoDataを渡しています。.selfを忘れず付与するようにしてください。JSONデコードについては以下の投稿も参考にしてください。

resume

新規に作成されたタスクはsuspended状態で始まるため、タスクを開始(使用)するためには、忘れずにresume()を用いる必要があります。実際、今回のコードからこの部分を削除するだけでも、データの取得ができなくなります。

resume() | Apple Developer Documentation
Resumes the task, if it is suspended.

Behavior

今回実装したコードを動かすと、以下のようになります。

まとめ

[ads]

今回、これ以上シンプルにはできないレベルでサンプルコードを作成してみました。実際の用途ではデータ構造がより複雑だったりと、そのままでは使えないと思いますが、こういった小さなコードから始めることで理解が深まればと思います。

SwiftUIにおける、Webアクセスに関して以下で詳細に解説していますので、今回のサンプルコードがブラックボックスのように思える方は一読していただけると理解が深まるかもしれません。

サンプルコード

import SwiftUI

struct ApiView: View {
    @State private var todo: TodoData = TodoData(userId: -1, id: -1, title: "na", completed: false)
    
    struct TodoData: Codable {
        var userId: Int
        var id: Int
        var title: String
        var completed: Bool
    }
    
    var body: some View {
        VStack(spacing: 10) {
            Text("Get Data")
                .padding(10)
                .background(Color(.green))
                .cornerRadius(20)
                .onTapGesture {
                    getData()
                }
            Text(todo.title)
        }
    }
    
    func getData() {
        guard let url = URL(string: "https://jsonplaceholder.typicode.com/todos/1") else { return }
        URLSession.shared.dataTask(with: url) {(data, response, error) in
            do {
                if let todoData = data {
                    let decodedData = try JSONDecoder().decode(TodoData.self, from: todoData)
                    self.todo = decodedData
                } else {
                    print("No data", data as Any)
                }
            } catch {
                print("Error", error)
            }
        }.resume()
    }
}

struct ApiView_Previews: PreviewProvider {
    static var previews: some View {
        ApiView()
    }
}
Ads