2
4

More than 1 year has passed since last update.

SwiftUIで株価をチャートで表示する 「API取得編」

Last updated at Posted at 2021-10-05

APIを叩いて非同期処理で株価を取得する

本記事の目的は、SwiftUI初心者がAlpha Vantage Inc.のAPIを使用して、米株市場の銘柄を取得できるようになることです。株価のチャートを表示するのは別の記事で投稿しようと思います。
5株価イメージ.png

APIKeyの取得 ~ APIの詳細について

APIKeyの取得手順
1.Alpha Vantage Inc.のサイトから「GET YOUR FREE API KEY TODAY」をクリックする。
1(APIKey取得).png
2.適当な情報を入力する。
2(APIKey取得).png

3.正常に処理が実行されるとAPIKeyが表示された画面に遷移するため、APIKeyをどこかにコピーする。

APIの詳細について
どのような状態のものが取得できるのか、想像していたよりも詳細に記載されていて驚きました。言語固有のガイドなどあったのも(少ないですが)良かったと思います。
今回は、「TIME_SERIES_DAILY」を使用しました。
Json形式で5ヶ月前までの、毎日の価格(始値、高値、安値、終値)と出来高を取得できるというものです。

実際の処理について

APIの取得で使用するファイルの構成は以下のようになっています。

HomeView.swift:ユーザーが操作するためのUIを定義したファイル
JsonData.swift:Jsonをデコードするための構造体を定義したファイル
AlphavantageViewModel.swift:非同期処理でAPI取得するため処理を定義したファイル

HomeView.swiftについて
今回の記事では、チャートは表示しないため、HomeView.swiftは一部省略した形となっています。次の記事で取得した株価をチャートにして表示させます。
変数で定義したapikeyは、取得したAPIKeyを直書きして下さい。本来コードに設定値などをハードコーディングするのはよろしくないです。別ファイル(xmlファイルなど)で定義することによって管理することをお勧めします。

HomeView.swift
import SwiftUI
import SwiftUICharts

struct HomeView: View {

    //テキストエディターの編集フラグ
    @State private var editting = false
    //テキストエディターで入力された文字列
    @State var text: String = ""
    //使用するAPIKey
    @State var apikey: String = "yourapikey"
    //APIでJson形式をデコードした株価の情報が格納される
    @State var stock: Stock = Stock(metaData: MetaData(the1Information: "", the2Symbol: "", the3LastRefreshed: "", the4OutputSize: "", the5TimeZone: ""), timeSeriesDaily: [:])
    //非同期で更新される変数を扱えるようにする
    @ObservedObject var viewModel: AlphavantageViewModel = AlphavantageViewModel()

    var body: some View {

        VStack {
            ZStack(alignment: .leading) {
                TextField("", text: $text, onEditingChanged: { begin in
                    // 入力開始処理
                    if begin {
                        self.editting = true
                        text = ""
                        stock = Stock(metaData: MetaData(the1Information: "", the2Symbol: "", the3LastRefreshed: "", the4OutputSize: "", the5TimeZone: ""), timeSeriesDaily: [:])
                    // 入力終了処理
                    } else {
                        self.editting = false
                    }
                //テキストフィールドに入力されたら、イベント発火するようにする
                },onCommit:{
                    viewModel.request(company: text, apikey: apikey, completion: {
                        stock = viewModel.stockData
                    })
                })
            }
        }
    }
}

JsonData.swiftについて
APIで取得してきたJson形式のデータをデコードすることによって、swiftのオブジェクトとして扱えるようにします。
Json形式の構造体の定義ですが、初心者には取得するJsonの形式によっては複雑で、どのように定義したら良いか分からない状態になると思います。

初心者に限らず、時間がない!、全然頭が働かない時!ありますよね。
そんな時には、quicktypeを使用しましょう!!
jsonから特定の言語のモデルを作成してくれるツールです。
対応している言語は、C#、C++、java、javascript、typescript、python、swiftなどです。
以下のようなイメージです。
3(json定義イメージ).png
完全に信用するべきではないですが、参考にする程度でも良いかなと思います。

JsonData.swift
import Foundation
public struct Stock: Codable {
    let metaData: MetaData
    let timeSeriesDaily: [String: TimeSeriesDaily]

    enum CodingKeys: String, CodingKey {
        case metaData = "Meta Data"
        case timeSeriesDaily = "Time Series (Daily)"
    }
}

struct MetaData: Codable {
    let the1Information, the2Symbol, the3LastRefreshed, the4OutputSize: String
    let the5TimeZone: String

    enum CodingKeys: String, CodingKey {
        case the1Information = "1. Information"
        case the2Symbol = "2. Symbol"
        case the3LastRefreshed = "3. Last Refreshed"
        case the4OutputSize = "4. Output Size"
        case the5TimeZone = "5. Time Zone"
    }
}

struct TimeSeriesDaily: Codable {
    let the1Open, the2High, the3Low, the4Close: String
    let the5Volume: String

    enum CodingKeys: String, CodingKey {
        case the1Open = "1. open"
        case the2High = "2. high"
        case the3Low = "3. low"
        case the4Close = "4. close"
        case the5Volume = "5. volume"
    }
}

AlphavantageViewModel.swiftについて

AlphavantageViewModel.swift
import Foundation
import SwiftUI

class AlphavantageViewModel: ObservableObject{

    @Published var stockData: Stock = Stock(metaData: MetaData(the1Information: "", the2Symbol: "", the3LastRefreshed: "", the4OutputSize: "", the5TimeZone: ""), timeSeriesDaily: [:])

    func request(company: String, apikey: String, completion: @escaping () -> Void) {

        //アクセスするURLを作成する
        let urlString = "https://www.alphavantage.co/query?function=TIME_SERIES_DAILY&symbol=\(company)&apikey=\(apikey)"
        let url = URL(string: urlString)
        let req = URLRequest(url: url!)
        let task = URLSession.shared.dataTask(with: req) { (data, response, error) in
            guard let data = data else { return }
            do{
                // デコード処理
                let requestResults: Stock = try JSONDecoder().decode(Stock.self, from: data)
                // UIを変更する
                DispatchQueue.main.async {
                    self.stockData = requestResults
                    completion()
                }

            } catch let error {
                print(error)
            }
        }
        task.resume()
    }
}

コードベースの解説

HomeView.swiftについて重要な部分は、テキストフィールドで入力し終えた時に、onCommitの処理が行われることです。
onCommit内の処理で、AlphavantageViewModel.swiftで定義したrequestメソッドが呼び出されています。そこで、引数に変数(入力された銘柄、APIKey)が渡されています。
また、クロージャ(completion)を定義して、取得してきた株の情報をstockに格納しています。
あとはお好みで、stockをViewで反映させるだけです。

},onCommit:{
    viewModel.request(company: text, apikey: apikey, completion: {
        stock = viewModel.stockData
    })
})

JsonData.swiftについて重要な部分は、enumで囲った部分です。
Json形式でデコードするためには、取得してきた値と定義した変数を同じ命名にする必要があります。しかし、変数の定義にスペースや()は含めることができません。
CodingKeyを使用することによって、定義した変数がJsonで取得している値と同じ状態にしています。keyとValueのイメージ。

public struct Stock: Codable {
    let metaData: MetaData
    let timeSeriesDaily: [String: TimeSeriesDaily]

    enum CodingKeys: String, CodingKey {
        case metaData = "Meta Data"
        case timeSeriesDaily = "Time Series (Daily)"
    }
}

AlphavantageViewModel.swiftについて重要な部分はいくつかあります。
Jsonを取得して、デコードする処理はこちらを参考にしました。
まず、@Published属性を付与した変数があることです。
@PublishedはObservableObjectプロトコルに準拠したクラス内のプロパティを監視し、変化があった際にViewに対して通知(反映する)ことができます。

@Published var stockData: Stock = Stock(metaData: MetaData(the1Information: "", the2Symbol: "", the3LastRefreshed: "", the4OutputSize: "", the5TimeZone: ""), timeSeriesDaily: [:])

そして、監視したい変数(stockDataをViewで反映させるため)は、View側(HomeView.swift)で@ObservedObjectを使用して、以下のように定義する必要があります。

//非同期で更新される変数を扱えるようにする
@ObservedObject var viewModel: AlphavantageViewModel = AlphavantageViewModel()

クロージャがスコープを抜けても存在し続ける時に、@escaping属性をクロージャ(completion)に付与している。

func request(company: String, apikey: String, completion: @escaping () -> Void) {

DispatchQueue.main.asyncでUIを更新する処理を非同期処理で行なっています。

// UIを変更する
DispatchQueue.main.async {
    self.stockData = requestResults
    completion()
}

最後に

Swiftは初心者であるため、まだまだ理解できてない部分が沢山ありますが、分かりやすいように更新していきたいです。
株価をチャートにした状態が以下のようなイメージです。
4(完成イメージ).png

2
4
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
4